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线程才打印出来。
修改代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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(); } }
六、使用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来写
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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(); } }
不重要,比较勉强,就是练习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()); } }
---