1. volatile实现可见性(jdk 1.5后)
1. 可见性
如果一个线程对共享变量值的修改,能够及时的被其他线程看到,叫做共享变量的可见性。如果一个变量同时在多个线程的工作内存中存在副本,那么这个变量就叫共享变量
volatile如何实现可见性?
volatile变量每次被线程访问时,都强迫线程从主内存中重读该变量的最新值,而当该变量发生修改变化时,也会强迫线程将最新的值刷新回主内存中。这样一来,不同的线程都能及时的看到该变量的最新值。
多个线程同时对主内存的一个共享变量进行读取和修改时,首先会读取这个变量到自己的工作内存中成为一个副本,对这个副本进行改动之后,再更新回主内存中变量所在的地方。
(由于CPU时间片是以线程为最小单位,所以这里的工作内存实际上就是指的物理缓存,CPU运算时获取数据的地方;而主内存也就是指的是内存,也就是原始的共享变量存放的位置)
两条规定:
a.线程对共享变量的所有操作必须在工作内存中进行,不能直接操作主内存
b.不同线程间不能访问彼此的工作内存中的变量,线程间变量值的传递都必须经过主内存
如果一个线程1对共享变量x的修改对线程2可见的话,需要经过下列步骤:
a.线程1将更改x后的值更新到主内存
b.主内存将更新后的x的值更新到线程2的工作内存中x的副本
所以,要实现共享变量的可见性必须保证下列两点:
a.线程对工作内存中副本的更改能够及时的更新到主内存上
b.其他线程能够及时的将主内存上共享变量的更新刷新到自己工作内存的该变量的副本上
Java中可以通过synchronized、volatile、java concurrent类来实现共享变量的可见性
但是volatile不能保证变量更改的原子性:
比 如number++,这个操作实际上是三个操作的集合(读取number,number加1,将新的值写回number),volatile只能保证每一 步的操作对所有线程是可见的,但是假如两个线程都需要执行number++,那么这一共6个操作集合,之间是可能会交叉执行的,那么最后导致number 的结果可能会不是所期望的。
所以对于number++这种非原子性操作,推荐用synchronized:
synchronized(this){number++; }
如下代码:最后的number的结果不一定是500,有可能是比500小,因为number++不是一个原子性的操作,用volatile不能保证可见性
public class VolatileTest {public static int number = 0;public void increase(){try {Thread.sleep(300);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}number++;}/*** @param args*/public static void main(String[] args) {final VolatileTest test = new VolatileTest();for(int i = 0 ; i < 500 ; i++){new Thread(new Runnable() {@Overridepublic void run() {test.increase();}}).start();}//若当期依然有子线程没有执行完毕while(Thread.activeCount() > 1){Thread.yield();//使得当前线程(主线程)让出CPU时间片}System.out.println("number is " + number);}}
对于自增之类的非原子性操作,只能通过如下方式保证可见性:
a. synchronized
b. ReentrantLock
c. AtomicInteger
synchronized修改如下:
public void increase(){try {Thread.sleep(300);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}synchronized(this){number++;}}
ReentrantLock修改方式如下:
public class VolatileTest {public static int number = 0;public Lock lock = new ReentrantLock();public void increase(){try {Thread.sleep(300);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}lock.lock();try{number++;//这块的代码实际项目中可能会出现异常,所以要捕获}finally{lock.unlock();//用try finally块保证Unlock一定要执行}}。。。
}
AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。
修改如下:
package com.mooc.test;import java.util.concurrent.atomic.AtomicInteger;public class VolatileTest {public static AtomicInteger number = new AtomicInteger(0);public void increase(){try {Thread.sleep(300);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}number.getAndIncrement();//获得当前值并且加1}/*** @param args*/public static void main(String[] args) {final VolatileTest test = new VolatileTest();for(int i = 0 ; i < 500 ; i++){new Thread(new Runnable() {@Overridepublic void run() {test.increase();}}).start();}//若当期依然有子线程没有执行完毕while(Thread.activeCount() > 1){Thread.yield();//使得当前线程(主线程)让出CPU时间片}System.out.println("number is " + number.get());}}
2. volatile适用情况
a.对变量的写入操作不依赖当前值
比如自增自减、number = number + 5等(不满足)
b.当前volatile变量不依赖于别的volatile变量
比如 volatile_var > volatile_var2这个不等式(不满足)
3. synchronized和volatile比较
a. volatile不需要同步操作,所以效率更高,不会阻塞线程,但是适用情况比较窄
b. volatile读变量相当于加锁(即进入synchronized代码块),而写变量相当于解锁(退出synchronized代码块)
c. synchronized既能保证共享变量可见性,也可以保证锁内操作的原子性;volatile只能保证可见性