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!!!