volatile
- 两个线程之间互相通信的问题
- 1A2B3C4D的问题(公开课)—->之后会讲

- 第一个线程完成到某个位置的时候,要通知某个线程
- 一边加add一边检测
第一种写法
/** * 曾经的面试题:(淘宝?) * 实现一个容器,提供两个方法,add,size * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束 * * 分析下面这个程序,能完成这个功能吗? * @author mashibing */package com.mashibing.juc.c_020_01_Interview;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;public class T01_WithoutVolatile { List lists = new ArrayList(); public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { T01_WithoutVolatile c = new T01_WithoutVolatile(); 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(); }}
第二种写法
- 第一种写法不能满足需求—->不行、有问题(同步的角度)
- ArrayList不是线程安全的—->要用同步容器(加元素和size的更新必须得是同步的,参照c问题—->d也能够实现?)
- 要保证线程之间的可见性
- 第二个线程读的时候,可能读到的size是之后的,因为是先加进去再更新size,有可能在这之间被打断
- 没加同步—->要在add和size方法上加synchronized?不对???
- 第二个线程没有检测到size==5,因为线程之间不可见的原因—->第一个线程变了的话,第二个线程不能马上看见—->加volatile?……
- 既然是线程不安全的容器,那么为什么不会出现ConcurrentModificationException的异常???
- 什么时候会出现ConcurrentModificationException,foreach中只能遍历访问元素,不能修改元素???
- 同时修改、读取会出现ConcurrentModificationException?modcount在读取的时候会++?
- 用同步容器Collections.synchronizedList(new LinkedList<>())
- 只加volatile也能解决问题,但是具有偶然性,因为睡了一段时间正好错开了
- 更重要的是:volatile这里修饰的是一个列表的引用,照理说只能在引用改变时保持同步,而不能在引用所指向的对象发生发生改变时保持刷新
- ❓volatile修饰的引用指向的对象中的值发生了改变是观察不到的
/** * 曾经的面试题:(淘宝?) * 实现一个容器,提供两个方法,add,size * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束 * * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环, * 而且,如果在if 和 break之间被别的线程打断,得到的结果也不精确, * 该怎么做呢? * @author mashibing */package com.mashibing.juc.c_020_01_Interview;import java.util.Collections;import java.util.LinkedList;import java.util.List;import java.util.concurrent.TimeUnit;public class T02_WithVolatile { //添加volatile,使t2能够得到通知 // 加锁也是不行的,因为自己释放锁之后自己又会获得锁! // 线程缓存的可见性与sleep的关系!!! //volatile List lists = new LinkedList(); volatile List lists = Collections.synchronizedList(new LinkedList<>()); 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); /*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(); }}
wait、notify解决上述面试题
- notify是不释放锁的,假如用的是一把锁,是不行的
- 释放锁的方式:
- wait释放锁!
- synchronized代码块执行完毕释放锁!
下面这种方式仍然无法解决
/** * 曾经的面试题:(淘宝?) * 实现一个容器,提供两个方法,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 com.mashibing.juc.c_020_01_Interview;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;public class T03_NotifyHoldingLock { //wait notify //添加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) { T03_NotifyHoldingLock c = new T03_NotifyHoldingLock(); 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(); }}
正确解决方式
- notify的线程t1进行wait让出这把锁,才能让另一个线程继续
- t2中也要加个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 com.mashibing.juc.c_020_01_Interview;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;public class T04_NotifyFreeLock { //添加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) { T04_NotifyFreeLock c = new T04_NotifyFreeLock(); 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(); }}
总结
- volatile在没有把握时就不要用,除了面试的时候
- volatile尽量不要使用
- volatile最好修饰简单的,越简单越好,不要去修饰引用值
- wait和notify不一定所有情况下都需要成对出现—->根据自己的需求自己设置合适的同步策略即可!
- 好多线程阻塞用notifyAll,只有一个线程阻塞用notify
- notify是不释放锁的。notify唤醒其他线程之后,这里的其他线程还是需要拿到锁之后才能继续向下执行的。
- wait释放锁之后,还需要拿到锁才能继续往下执行
- 加锁后还出现问题的原因:释放锁后,因为自己执行的太快了,**自己又再次获得了锁!这让t2得不到恰当的判断时机**
- 这题的正确解法是用wait和notify来做,是一个线程之间的互斥同步问题,而不是去想直接用synchronized或者用volatile
- 考察的是线程的同步,而不是可见性问题(线程缓存sleep?)
- 考察的是线程之间的同步,即一个线程必须在另一个线程执行到什么步骤时输出,然后另一个线程才能继续执行
- sleep的作用是留时间给t2唤醒并输出(因为线程执行的太快了—->会造成即使能够正确可见,依旧会因为t1继续快速向下执行而造成t2输出的位置不对!!!)
- 尽量不要用volatile!!!
- 尽量不要用volatile!!!
- 尽量不要用volatile!!!