手记

实现线程可见性的两个关键字 synchronized和volatile

Java内存模型[JMM]:描述了Java程序中各种变量(这里的变量指的是线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层的细节。
工作内存、主内存:每个程序都有一个主内存,每个线程都有自己独立的工作内存,里面保存了该线程使用到的变量的副本(主内存该变量的一份拷贝)。不能直接访问主内存。线程间变量值的传递需要通过主内存完成。
共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个现成的共享变量。
可见性:一个线程对共享变量值的修改,能够及时地被其他线程看到。
+++++++++++++++++++++synchronized+++++++++++++++++++++
一、JMM关于synchronized的两条规定:
1.线程解锁前,必须把共享变量的最新值刷新到主内存中。
2.线程加锁前,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取.
二、线程执行互斥代码的过程:
1.获取互斥锁.
2.清空工作内存。
3.从主内存拷贝变量的最新副本到工作内存中。
4.执行代码.
5.将更改后的共享变量的值刷新的内存中。
6.释放互斥锁。
三、可见性分析
导致共享变量在线程间不可见的原因:
1.线程交叉执行
2.重排序结合线程交叉执行
3.共享变量更新后的值没有在工作内存和主内存间及时更新。
synchronized的解决方案:
1.原子性----锁内部的代码在一段时间内只能由一个线程执行,避免线程在锁内部交叉执行。
2.原子性----避免线程在锁内部交叉执行;重排序都是在单独的线程内执行,再结合as-if-serial理解。
3.可见性----这是synchronized的可见性规范。
+++++++++++++++++++++volatile+++++++++++++++++++++
一、 通过加入内存屏障和禁止重排序优化来实现。
1.对volatile变量执行写操作时,会在写操作后加入一条store屏障指令。强制更新主内存;防止处理器把volatile前面的变量重排序到volatile写变量操作之后;
2.对volatile变量指向读操作时,会在读操作前加入一条load屏蔽指令。同上。
二、 volatile如何实现内存可见性
volatile变量在每次被线程访问时,都强迫主内存中重读该变量的值,而该变量发生变化时,又会强迫线程将最新的值刷新到主内存。这样任何时刻,不同的线程总能看到该变量的最新值。
A 线程写volatile变量的过程
1.改变线程工作内存中volatile变量副本的值。
2.将改变后的副本的值从工作内存中刷新到新的内存中。
B 线程读volatile变量的过程
1.从主内存中最新volatile变量的值更新到线程的工作内存中。
2.从工作内存中读取volatile变量副本。
三、volatile不能保证volatile变量复合操作的原子性
private int num = 0;
num++;//不是原子操作
num++分解成三个步骤:读取num的值--->将num的值 + 1 --->将最新num的值写入内存
1、 加入synchronized代码块
synchronized(this) {
num++;
}
加入synchronized后变成原子操作,换句话说,num++分解出来的三个步骤只能被一个线程执行完之后才能被另外一个线程执行。
2、改成volatile变量
private volatile int num = 0;
无法保证原子性。
3.保证num自增操作的原子性
·使用synchronized关键字:
public void increase() {
try{
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(this) {
this.num++;
}
}
·使用jdk1.5以后的ReentrantLock重入锁(java.util.concurrent.locks包下):
private Lock lock = new ReentrantLock();
public void increase() {
try{
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try{
this.num++;
} finally {
lock.unlock();
}

}
lock()和unlock()其实就是进入和退出synchronized代码块。
·使用jdk1.5以后的AtomicInteger(java.util.concurrent.atomic包下)
四、使用volatile的注意事项:
要在多线程中安全地使用volatile变量,必须满足:
1.对volatile变量的写入操作不依赖其当前值:比如count = count * 5;
2.该变量没有包含在具有其他变量的不变式中:比如程序中有两个volatile变量,不变式low<up【一个公式初始为真,如果在一系列迭代过程中总保持为真,那么便称之为不变式】;
五、synchronized和volatile的比较:
a.volatile不需加锁,比synchronized更轻量,不会阻塞线程,效率也就更高;
b.从可见性角度看,对volatile变量的读操作实际就是进入synchronized代码块,也就是加锁;而对于volatile变量的写操作实际就是退出synchronized代码块,也就是解锁;//这个地方不太懂
c.synchronized既保证可见性,又保证原子性,volatile只保证可见性。
所以在可以保证线程安全性的前提,尽可能使用volatile。

1人推荐
随时随地看视频
慕课网APP

热门评论

java没有提供检测与避免死锁的专门机制,但应用程序员可以采取某些策略防止死锁的发生。

共享数据的访问权限必须是private

但是并不是说不加关键字 就一定没有可见性,由于编译器优化功能,有时也会提供可见性。

查看全部评论