java高并发编程(二)

马士兵java并发编程的代码,照抄过来,做个记录。

 

一、分析下面面试题

/**
 * 曾经的面试题:(淘宝?)
 * 实现一个容器,提供两个方法,add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 * 
 * 分析下面这个程序,能完成这个功能吗?
 * @author mashibing
 */
package yxxy.c_019;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class MyContainer1 {

    List lists = new ArrayList();

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }
    
    public static void main(String[] args) {
        MyContainer1 c = new MyContainer1();

        new Thread(() -> {
            for(int i=0; i<10; i++) {
                c.add(new Object());
                System.out.println("add " + i);
                
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();
        
        new Thread(() -> {
            while(true) {
                if(c.size() == 5) {
                    break;
                }
            }
            System.out.println("t2 结束");
        }, "t2").start();
    }
}
分析:
不能完成这个功能;
添加volatile关键字,修改为如下:
 
二、添加volatile:
/**
 * 曾经的面试题:(淘宝?)
 * 实现一个容器,提供两个方法,add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 * 
 * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
 * @author mashibing
 */
package yxxy.c_019;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class MyContainer2 {

    //添加volatile,使t2能够得到通知
    volatile List lists = new ArrayList();

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }
    
    public static void main(String[] args) {
        MyContainer2 c = new MyContainer2();

        new Thread(() -> {
            for(int i=0; i<10; i++) {
                c.add(new Object());
                System.out.println("add " + i);
                
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();
        
        new Thread(() -> {
            while(true) {
                if(c.size() == 5) {
                    break;
                }
            }
            System.out.println("t2 结束");
        }, "t2").start();
    }
}

但是上面代码还存在两个问题:

1)由于没加同步,c.size()等于5的时候,假如另外一个线程又往上增加了1个,实际上这时候已经等于6了才break,所以不是很精确;

2)浪费CPU,t2线程的死循环很浪费cpu

3)ArrayList不是线程安全的,add和size方法应该是要同步方法,比如add到第5个的时候,size还没来得及加1,这时候别的线程来读size=4,其实已经是5个了,读到的是错误的size,因此要使用加锁的容器:

volatile List lists = Collections.synchronizedList(new ArrayList());

 

二.1、volatile其实没有起作用

package interview;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 曾经的面试题:(淘宝)
 * 实现一个容器,提供两个方法,add size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 *
 * 分析下面程序,能完成这个功能吗?
 * volatile其实没有起作用,将t1线程里面的睡眠1s去掉后,运行,t2还是无法检测到size为5.
 * 为什么呢,因为这里volatile修饰的是一个引用,我们改的是引用里面的值,但是这个引用的值本身没变,所以加volatile不起作用。
 * 1.volatile如果你没有把握就不要用
 * 2.volatile尽量修饰简单的值,不要修饰引用值。volatile只是关注的是引用的值,引用指向的对象里面的内容改变了,它是观察不到的
 */
public class T02_WithVolatile {

    volatile List lists = Collections.synchronizedList(new ArrayList());

    public void add(Object o) {
        lists.add(o);
    }

    public int size(){
        return lists.size();
    }

    public static void main(String[] args) {
        T02_WithVolatile c = new T02_WithVolatile();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add " + i);
            }
        }, "t1").start();

        new Thread(() -> {
            while (true){
                if(c.size() == 5){
                    break;
                }
            }
            System.out.println("t2 结束");
        }, "t2").start();
    }
}

 

三、使用wait和notify

/**
 * 曾经的面试题:(淘宝?)
 * 实现一个容器,提供两个方法,add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 * 
 * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
 * 
 * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
 * 
 * 阅读下面的程序,并分析输出结果
 * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
 * 想想这是为什么?
 * @author mashibing
 */
package yxxy.c_019;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class MyContainer3 {

    //添加volatile,使t2能够得到通知
    volatile List lists = new ArrayList();

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }
    
    public static void main(String[] args) {
        MyContainer3 c = new MyContainer3();
        
        final Object lock = new Object();
        
        new Thread(() -> {
            synchronized(lock) {
                System.out.println("t2启动");
                if(c.size() != 5) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t2 结束");
            }
            
        }, "t2").start();
        
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        new Thread(() -> {
            System.out.println("t1启动");
            synchronized(lock) {
                for(int i=0; i<10; i++) {
                    c.add(new Object());
                    System.out.println("add " + i);
                    
                    if(c.size() == 5) {
                        lock.notify();
                    }
                    
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "t1").start();
        
        
    }
}

分析:

1)解释wait和notify方法:

使用wait和notify必须进行锁定,如果没有锁定这个对象,你就不能调这个对象的wait和notify方法;
wait和notify是调用被锁定对象的wait和notify方法。
比如有一个对象A,两个线程1,2来访问这个对象A,第一个线程1的访问过程中假如条件没有满足,想让这个线程1暂停,等着;这时候怎么办的呢?
首先线程1要锁定对象A,然后调用这个对象A的wait方法,线程1就进入等待状态,同时释放锁,别的线程可以进来;
 
什么时候线程1会再次启动呢?只有再调用这个对象A的notify方法。notify方法会启动一个正在这个对象A上等待的某一个线程,或者是调用notifyAll(),
notifyAll方法是叫醒在这个对象上正在等待的所有线程。
 
notify无法指定的通知启动哪一个具体的线程,没法指定,由cpu线程调度器自己帮你找一个线程运行。
 
 
 
2)为什么size=5了,t2线程没有结束?
由于notify不会释放锁,即便你通知了t2,让它起来了,它起来之后想往下运行,wait了之后想重新继续往下运行是需要重新得到lock这把锁的,可是很不幸的是t1已经把这个锁锁定了,所以只有等t1执行完了,t2才会继续执行。

 

 

 

四、改进wait和notify

/**
 * 曾经的面试题:(淘宝?)
 * 实现一个容器,提供两个方法,add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 * 
 * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
 * 
 * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
 * 
 * 阅读下面的程序,并分析输出结果
 * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
 * 想想这是为什么?
 * 
 * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
 * 整个通信过程比较繁琐
 * @author mashibing
 */
package yxxy.c_019;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class MyContainer4 {

    //添加volatile,使t2能够得到通知
    volatile List lists = new ArrayList();

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }
    
    public static void main(String[] args) {
        MyContainer4 c = new MyContainer4();
        
        final Object lock = new Object();
        
        new Thread(() -> {
            synchronized(lock) {
                System.out.println("t2启动");
                if(c.size() != 5) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t2 结束");
                //通知t1继续执行
                lock.notify();
            }
            
        }, "t2").start();
        
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        new Thread(() -> {
            System.out.println("t1启动");
            synchronized(lock) {
                for(int i=0; i<10; i++) {
                    c.add(new Object());
                    System.out.println("add " + i);
                    
                    if(c.size() == 5) {
                        lock.notify();
                        //释放锁,让t2得以执行
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "t1").start();
        
        
    }
}

流程图:

 

上面的通信过程太繁琐了,有没有简单点的办法?

 

 

 

五、使用countDownLatch简化

/**
 * 曾经的面试题:(淘宝?)
 * 实现一个容器,提供两个方法,add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 * 
 * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
 * 
 * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
 * 
 * 阅读下面的程序,并分析输出结果
 * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
 * 想想这是为什么?
 * 
 * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
 * 整个通信过程比较繁琐
 * 
 * 使用Latch(门闩)替代wait notify来进行通知
 * 好处是通信方式简单,同时也可以指定等待时间
 * 使用await和countdown方法替代wait和notify
 * CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行
 * 当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了
 * 这时应该考虑countdownlatch/cyclicbarrier/semaphore
 * @author mashibing
 */
package yxxy.c_019;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class MyContainer5 {

    // 添加volatile,使t2能够得到通知
    volatile List lists = new ArrayList();

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }

    public static void main(String[] args) {
        MyContainer5 c = new MyContainer5();

        CountDownLatch latch = new CountDownLatch(1);

        new Thread(() -> {
            System.out.println("t2启动");
            if (c.size() != 5) {
                try {
                    latch.await();
                    
                    //也可以指定等待时间
                    //latch.await(5000, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t2 结束");

        }, "t2").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        new Thread(() -> {
            System.out.println("t1启动");
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add " + i);

                if (c.size() == 5) {
                    // 打开门闩,让t2得以执行
                    latch.countDown();
                }

                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }, "t1").start();

    }
}
 
CountDownLatch(1),CountDown往下数,当1变为0的时候门闩就开了,latch.countDown()调用一次数就往下-1;
latch.await(),门闩的等待是不需要锁定任何对象的;
 
但是上面写法还是有问题的,去掉t1线程里面的睡眠1s就会发现add 5之后并没有准确的在size=5的时候打印t2结束,为什么呢
t1的size=5的时候确实将门闩打开了,可是t1马上蹭蹭蹭继续往下运行,size继续加,很可能加到7,8,9... t2线程才打印出来。 
修改代码如下:
package interview;


/**
 * 曾经的面试题:(淘宝?)
 * 实现一个容器,提供两个方法,add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 *
 * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
 *
 * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
 *
 * 阅读下面的程序,并分析输出结果
 * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
 * 想想这是为什么?
 *
 * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
 * 整个通信过程比较繁琐
 *
 * 使用Latch(门闩)替代wait notify来进行通知
 * 好处是通信方式简单,同时也可以指定等待时间
 * 使用await和countdown方法替代wait和notify
 * CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行
 * 当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了
 * 这时应该考虑countdownlatch/cyclicbarrier/semaphore
 * @author mashibing
 */
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class T06_CountDownLatch2 {

    List lists = Collections.synchronizedList(new ArrayList());

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }

    public static void main(String[] args) {
        T06_CountDownLatch2 c = new T06_CountDownLatch2();

        CountDownLatch latch1 = new CountDownLatch(1);
        CountDownLatch latch2 = new CountDownLatch(1);

        new Thread(() -> {
            System.out.println("t2启动");
            if (c.size() != 5) {
                try {
                    latch1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t2 结束");
            latch2.countDown();

        }, "t2").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        new Thread(() -> {
            System.out.println("t1启动");
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add " + i);

                if (c.size() == 5) {
                    // 打开门闩,让t2得以执行
                    latch1.countDown();
                    try {
                        latch2.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }, "t1").start();

    }
}
View Code

 

六、使用LockSupport简化
package interview;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * 曾经的面试题:(淘宝?)
 * 实现一个容器,提供两个方法,add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 * <p>
 * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
 * <p>
 * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
 * <p>
 * 阅读下面的程序,并分析输出结果
 * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
 * 想想这是为什么?
 * <p>
 * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
 * 整个通信过程比较繁琐
 * <p>
 * 使用LockSupport来简化
 *
 * @author mashibing
 */
public class T04_LockSupport {

    List lists = Collections.synchronizedList(new ArrayList());

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }

    static Thread t1 = null, t2 = null;

    public static void main(String[] args) {
        T04_LockSupport c = new T04_LockSupport();

        t1 = new Thread(() -> {
            System.out.println("t1启动");
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add " + i);

                if (c.size() == 5) {
                    LockSupport.unpark(t2);
                    LockSupport.park();
                }
            }
        }, "t1");

        t2 = new Thread(() -> {
            System.out.println("t2启动");
            LockSupport.park();
            System.out.println("t2 结束");
            LockSupport.unpark(t1);
        }, "t2");

        t2.start();
        t1.start();
    }
}

 

七、使用Semaphore来写

package interview;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 曾经的面试题:(淘宝?)
 * 实现一个容器,提供两个方法,add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 * <p>
 * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
 * <p>
 * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
 * <p>
 * 阅读下面的程序,并分析输出结果
 * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
 * 想想这是为什么?
 * <p>
 * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
 * 整个通信过程比较繁琐
 * <p>
 * 使用Semaphore来写这个程序
 *
 * @author mashibing
 */
public class T07_Semaphore {

    List lists = Collections.synchronizedList(new ArrayList());

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }

    public static void main(String[] args) {
        T07_Semaphore c = new T07_Semaphore();

        Semaphore s = new Semaphore(1);

        Thread t2 = new Thread(() -> {
            System.out.println("t2启动");
            try {
                s.acquire();
                System.out.println("t2 结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                s.release();
            }

        }, "t2");


        new Thread(() -> {
            System.out.println("t1启动");
            try{
                s.acquire();
                t2.start();
                for (int i = 0; i < 10; i++) {
                    c.add(new Object());
                    System.out.println("add " + i);

                    if (c.size() == 5) {
                        s.release();
                        t2.join();
                        s.acquire();
                    }
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                s.release();
            }

        }, "t1").start();

    }
}
View Code

不重要,比较勉强,就是练习Semaphore的使用,开阔思路。

1.为了保证t1线程先启动,并且acquire到,所以在t1线程启动里面s.acquire之后调用t2.start()

2.为了保证size=5之后t2执行,使用了t2.join()

 
八、经典面试题:要求用线程顺序打印A1B2C3....Z26
package interview;

import java.util.concurrent.CountDownLatch;

/**
 * 要求用线程顺序打印A1B2C3....Z26
 *
 * 博客园:https://www.cnblogs.com/kiwi-deng/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%8E%E9%AB%98%E5%B9%B6%E5%8F%91/
 */
public class T08_Question {
    public static void main(String[] args) {
        String[] numbers = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"};
        String[] words = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"};

        final Object lock = new Object();
        StringBuilder sb = new StringBuilder();
        CountDownLatch latch = new CountDownLatch(2);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < words.length; i++) {
                synchronized (lock) {
                    sb.append(words[i]);
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.notify();
                }
            }
            latch.countDown();
        });

        Thread t2 = new Thread(() -> {
            for (int j = 0; j < numbers.length; j++) {
                synchronized (lock) {
                    sb.append(numbers[j]);
                    lock.notify();
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            latch.countDown();
        });

        t1.start();
        t2.start();

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(sb.toString());
    }
}

 

 
 
 
 
 
 
---
 

 

posted on 2018-07-21 01:30  有点懒惰的大青年  阅读(571)  评论(0编辑  收藏  举报