前两天参加了一个电话面试,面试官问了很多问题,挑一些印象深刻的记录分享一下。其中一个问题是:Java多线程怎么保证变量的可见性?听到问题之后,我懵了……
在网上搜索学习了一下,简单概括如下:
Java中可以通过synchronized、volatile、java concurrent类来实现共享变量的可见性。
1.synchronizedsynchronized 实际上是对访问修改共享变量的代码块进行加互斥锁,多个线程对synchronized代码块的访问时,某一时刻仅仅有一个线程在访问和修改代码块中的内 容(加锁),其他所有的线程等待该线程离开代码块时(释放锁)才有机会进入synchronized代码块。
所以某一个线程进入synchronized代码块前后,执行过程入如下:
a.线程获得互斥锁
b.清空工作内存
c.从主内存拷贝共享变量最新的值到工作内存成为副本
d.执行代码
e.将修改后的副本的值刷新回主内存中
f.线程释放锁
随后,其他代码在进入synchronized代码块的时候,所读取到的工作内存上共享变量的值都是上一个线程修改后的最新值。
*共享变量不可见主要有下列原因:
a.线程的交叉执行
b.重排序
c.共享变量未能及时更新
通过使用synchronized可以保证原子性(synchronized代码块内容要么不执行,要执行就保证全部执行完毕)和可见性。
2.volatilevolatile变量每次被线程访问时,都强迫线程从主内存中重读该变量的最新值,而当该变量发生修改变化时,也会强迫线程将最新的值刷新回主内存中。这样一来,不同的线程都能及时的看到该变量的最新值。但是volatile不能保证变量更改的原子性:
比 如number++,这个操作实际上是三个操作的集合(读取number,number加1,将新的值写回number),volatile只能保证每一 步的操作对所有线程是可见的,但是假如两个线程都需要执行number++,那么这一共6个操作集合,之间是可能会交叉执行的,那么最后导致number 的结果可能会不是所期望的。
所以对于number++这种非原子性操作,推荐用synchronized。
*对于自增之类的非原子性操作,只能通过如下方式保证可见性:
a. synchronized
b. ReentrantLock
c. AtomicInteger
*volatile适用情况
a.对变量的写入操作不依赖当前值
b.当前volatile变量不依赖于别的volatile变量
3. synchronized和volatile比较a. volatile不需要同步操作,所以效率更高,不会阻塞线程,但是适用情况比较窄
b. volatile读变量相当于加锁(即进入synchronized代码块),而写变量相当于解锁(退出synchronized代码块)
c. synchronized既能保证共享变量可见性,也可以保证锁内操作的原子性;volatile只能保证可见性



