(一)并发中的变量可见性问题
1.变量分类?
全局变量
局部变量
属性(静态的、非静态的) 全局变量
本地变量 局部变量
参数 局部变量
2、如何在多个线程间共享数据?
用全局变量:静态变量,或共享对象
3.一个变量在线程1中被改变值了,在线程2中能看到该变量的最新值吗?
import java.util.concurrent TimeUnit
public class VisbilityDemo{
//状态标识
private static boolean is=true;
Public static void main(String[] args){
new Thread(new Runable(){
public void run(){
int i=0;
while(VisibilityDemo.is) {
i++;
}
System.out.println(i);
}
}),start();
try {
TimeUnit.SECONDS.slee(2);
}catch (InterruptedException e){
e.printStackTrace();
}
//设置is为false,使上面的线程结束while循环
VisibilityDemo.is = false;
System.out.println("被设置为false了");
}
}
线程会停止循环,打印出i的值吗?不会
*并发的线程能不能看到变量的最新值,这就是并发中的变量可见性问题
怎样才能可见?
方式一:同步关键字:synchronized
方式二:volatile
(二)变量可见性、线程安全问题原因
1.Java内存模型
Java内存模型以及操作规范变量
1.共享变量必须存放在主存中;
2.线程有自己的工作内存,线程只可操作自己的工作内存;
3.线程要操作共享变量,需从主存中读取到工作内存,改变值后需从工作内存同步到主存中。
Java内存模型会带来什么问题?
*有变量A,多线程并发对其累加会有什么问题?如三个线程并发操作变量A,大家读取A时读到A=0,
都对A+1,在将值同步会主内存,结果是多少?
结果是1.
这就是线程安全问题。
如何解决线程安全问题?
内存模型也产生了变量可见性问题。
如何让线程2使用A时看到最新值?
1.线程1修改A后必须马上同步回主内存
2.线程2使用A前需从主内存读取到工作内存。
疑问一:使用前不会重新从主内存读取到工作内存吗?
疑问二:修改后不会立马同步会主内存吗?
2.Java内存模型–同步交互协议
规定了8种原子操作:
1.lock(锁定):将主内存中的变量锁定,为一个线程所独占
2.unlock(解锁):将lock加的锁定解除,此时其它的线程可以有机会访问此变量
3.read(读取):作用于主存变量,将主存中的变量读到工作内存当中
4.load(载入):作用于工作内存变量,将read读取的值保存到工作内存中的变量副本中
5.use(使用):作用于工作内存变量,将值传递给线程的代码执行引擎
6.assign(赋值):作用于工作内存变量,将执行引擎处理返回的值重新赋值给变量副本
7.store(存储):作用于工作内存变量,将变量副本的值传送到主内存中
8.write(写入):作用于主内存变量,将store传送过来的值写入到主内存的共享变量中
操作规范:
1.将一个变量从主内存复制到工作内存要顺序执行read load操作;要将变量从工作内同步
回主内存要顺序执行store、write操作。只要求顺序执行,不一定是连续执行
2.做了assign操作,必须同步回主存。不能没做assign,同步回内存。
并发中保持变量可见性的方式:
1.final变量
//final不可变变量
private final int var1 = 1;
2.Synchronized
while (VisibilityDemo.is){
synchronized(this){
i++;
}
}
3.用volatile修饰
//状态标识
private static volatitle boolean is =true;
1synchronized怎样做到可见性?
1synchronized语义规范:
1.进入同步块前,先清空工作内存中的共享变量,从主内存中重新加载。
2.解锁前必须把修改的共享变量同步回主内存
1synchronized如何做到线程安全的?
1.锁机制保护共享资源,只有获得锁的线程才可操作共享资源;
2.synchronized语义规范保证了修改共享资源后,会同步回主存,就做到了线程安全。
(三)volatile关键字解密
1.volatile怎样做到可见性
volatile语义规范:
1.使用volatile变量时,必须重新从主内存加载,并且read 、load是连续的。
2.修改volatile变量后,必须立马同步回主内存,并且store、 write是连续的。
volatile能做到线程安全吗?
1.不能,因为它没有锁机制,线程可并发操作共享资源。
1为什么使用volatile?
synchronized可以保证可见性,为什么要用volatile?
1.主要原因:使用volatitle比synchronized简单
2.volatile性能比synchronized稍好。
*如果是保证可见性,用volatile;保证线程安全用synchronized。
volatile还有什么用途?
volatile可用于限制局部代码指令重排序
1.线程A
content = initCoutent(); //(1)
islint = true;//(2)
线程B
is(islint){//(3)
content.operation();//(4)
}
2.jvm优化指令排序后,代码的执行顺序可能如下:
线程A
islint = true;//(1)
content = initCoutent(); //(2)
3.当两个线程并发执行时,就可能出现线程B中抛空指针异常。
4.当我们在变量上加volatile修饰时,则用到该变量的块中就不会进行指令重排优化。
volatile的使用场景
volatitle的使用范围:
1.volatile只可修饰成员变量(静态的、非静态的)【全局变量】
2.多线程并发下,才需要使用它。
volatitle典型的应用场景:
1、只有一个修改者,多个使用者,要求保证可见性的场景
2、状态标识,如示例中介绍的标识
2、数据定期发布,都个获取者
volatile保证并发的情况下,不需要太多的线程进入到锁机制