线程可见性和线程安全性有什么关联?
我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改状态,而且还希望确保当一个线程修改状态后,其他线程能够看到发生的状态变化
概念层面的可见性
-
什么是线程间的可见性?
一个线程对共享变量值的修改,能够及时的被其他线程看到。 -
什么是共享变量?
如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。 -
什么是java内存模型?(Java Memory Model,简称JMM)
image.png
JMM描述了java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节。
规则1:
1> 所有的变量都存储在主内存中
2> 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)
规则2:
1> 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写
2> 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量的传递需要通过主内存来完成
共享变量可见性实现的原理:
线程1对共享变量的修改要想被线程2及时看到,必须经过如下2个步骤:
1> 把工作内存1中更新过的共享变量刷新到主内存中
2> 将主内存中最新的共享变量的值更新到工作内存2中
java语言层面支持的可见性实现方式有以下两种:
1> synchronized
2> volatile
注意:synchronized实现可见性以及原子性,
而volatile只能保证变量的可见性,就是不做下面互斥代码过程的第2步操作,没有锁且不会清空工作内存;只发挥了第5步的作用:及时将在工作内存中更新的变量刷到主内存中去。
JMM关于synchronized的两条规定:
1> 线程解锁前(退出synchronized代码块之前),必须把共享变量的最新值刷新到主内存中,也就是说线程退出synchronized代码块值后,主内存中保存的共享变量的值已经是最新的了
2> 线程加锁时(进入synchronized代码块之后),将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁需要是同一把锁)
两者结合:线程解锁前对共享变量的修改在下次加锁时对其他线程可见
根据以上推出线程执行互斥代码的过程:
1> 获得互斥锁(进入synchronized代码块)
2> 清空工作内存
3> 从主内存拷贝变量的最新副本到工作内存
4> 执行代码
5> 将更改后的共享变量的值刷新到主内存
6> 释放互斥锁(退出synchronized代码块)
4. 什么是指令重排序?
代码书写的顺序与实际执行的顺序不同,指令重排序是编译器或处理器为了提高程序性能而做的优化(有些代码翻译成机器指令之后,如果进行一个重排序,那可能重排序之后的顺序更加符合CPU执行的特点,这样就可以最大限度发挥CPU的性能)
1> 编译器优化的重排序(编译器)
2> 指令级并行重排序(处理器优化)
3> 内存系统的重排序(处理器优化)
5. 什么是as-if-serial语义?
无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致(java编译器、运行时和处理器都会保证java在单线程下遵循as-if-serial语义)
int num1=1;
int num2=2;
int sum=num1+num2;
单线程:第1、2行的顺序可以重排,但第3行不能
6. ReentrantLock锁
除了用synchronized解决共享变量多线程问题,还可以使用ReentrantLock锁,具体区别我们以后再说。
ReentrantLock lock = new ReentrantLock();
lock.lock();
//lock.tryLock(timeout, unit); 设置锁时间
//业务代码
lock.unlock();
7. 分布式系统如何实现可见性(分布式锁)?
redis、zk、Chubby
网友评论