volatile 这个关键字,编程的时候很少用到,有些地方说他是轻量级的synchronized,这个说法其实是不对的!!!
首先,两者使用的前提都是 ,多个线程会访问并更改同一个变量。
从两者的语义上来说,首先
synchronized : 在其修饰的代码块在多线程并发的情况下,执行的时候
1. 能保证其修饰的代码块一个挨着一个执行,就是一个线程执行完,下一个才执行。
2,synchronized 保证,其修饰的代码段在执行前,会把共享变量的状态同步到线程自己的运行内存,在synchronized执行完后,立即把线程对自己运行内存的修改同步到主存
所以,可以看做synchronized修饰后,各个线程是挨个对主存进行读取,操作,修改。来保证高并发下线性执行。
volatile 语义
volatile 语义只和上述synchronized的第二点是一样的,即多线程在读取并操作它修饰的变量时候,读的时候都会去主存读取最新值,修改完马上会同步到主存。
但是volatile 变量的操作如果不是原子操作,并不能保证上述synchronized 的第一个语义!!!
所以认为想用volatile 一个变量 来保证多线程情况下对他的操作,不会丢失,是无法保证的,看下面程序的i++ 操作
====================分割线=============================
package com.concurrenttest;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
/**
* @author 孟皓佶
* 验证volatile 变量可见性
* 验证volatile 并不保证同步操作变量
*/
public class VolatileTest {
private static Logger logger = Logger.getLogger(VolatileTest.class);
private static volatile int i;
private static int totalLoop=50;
private static int totalThread=3;
public static void main(String[] args) {
Runnable run = new Runnable(){
@Override
public void run() {
logger.info("线程"+Thread.currentThread().getName()+" 开始");
for(int j=0;j<totalLoop;j++){
/*这里对volatile 变量++
*i++不是原子操作,分为读取i值,再i=i+1
*线程有自己的运行内存空间
*volatile语义
* 内存屏障
* volatile语义1.线程各自的运行空间需要读取i的时候,都会从主存读取最新的值过来
* (这个时候对i的+1操作,其实是线程对自己内存的i+1
* volatile语义2 volatile的第二个作用是线程每次对volatile的变量的改变,在完成后,都会立即同步到主存
*
* !!volatile只能保证变量的变化在线程间立即可见,并不保证对共享变量操作的同步性
*/
i++;
//这里就同步到主存了
try {
//为了便于观察,sleep 下
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
logger.info("线程"+Thread.currentThread().getName()+" 结束");
}
};
ThreadGroup threadGroup = new ThreadGroup("concurrent");
logger.info("========初始======"+i+"================");
logger.info("同时开始"+totalThread+"个线程,每个线程对volatile变量++"+totalLoop+"次");
//这里同时启动三个线程并发
for(int threadCount = 0;threadCount<totalThread;threadCount++){
Thread thread = new Thread(threadGroup,run);
thread.start();
}
waitFinish(threadGroup);
//等待所有线程结束,输出最后结果
logger.info("=======结束======="+i+"================");
}
private static void waitFinish(ThreadGroup threadGroup) {
while (threadGroup.activeCount() > 0) {
try {
TimeUnit.MICROSECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
==============运行结果=======================================
2017-10-13 00:36:30,148 INFO main (com.concurrenttest.VolatileTest.main:52) - ========初始======0================
2017-10-13 00:36:30,151 INFO main (com.concurrenttest.VolatileTest.main:53) - 同时开始3个线程,每个线程对volatile变量++50次
2017-10-13 00:36:30,152 INFO Thread-0 (com.concurrenttest.VolatileTest.run:22) - 线程Thread-0 开始
2017-10-13 00:36:30,152 INFO Thread-2 (com.concurrenttest.VolatileTest.run:22) - 线程Thread-2 开始
2017-10-13 00:36:30,152 INFO Thread-1 (com.concurrenttest.VolatileTest.run:22) - 线程Thread-1 开始
2017-10-13 00:36:35,153 INFO Thread-1 (com.concurrenttest.VolatileTest.run:45) - 线程Thread-1 结束
2017-10-13 00:36:35,154 INFO Thread-0 (com.concurrenttest.VolatileTest.run:45) - 线程Thread-0 结束
2017-10-13 00:36:35,153 INFO Thread-2 (com.concurrenttest.VolatileTest.run:45) - 线程Thread-2 结束
2017-10-13 00:36:35,156 INFO main (com.concurrenttest.VolatileTest.main:62) - =======结束=======148================
=============分析===========================
三个线程同时对volatile修饰的共享变量i 进行i++操作各50次,结果 = 148 ,少了两个,为了观察方便,每次++ 完成后 sleep 100ms ,这样5s 左右全部线程运行完毕。
也许这样还看不清楚,来考虑极端的情况,下面的程序将加入一个新的线程叫badBoy , badBoy 取得 共享变量i 之后将i 值赋值给自己线程内存的value,休眠4s,
后,把value+1 赋回给共享变量i,由于i是volatile 修饰的,所以对i的赋值立即同步到主存,之所以让badboy 休眠4s,是这个时候其他4个线程还在运行,badboy之所以叫
badboy,是因为模拟他对i 操作很慢,这个看程序的输出是什么,应该是别的线程读到了这个i,最终的结果会远小于前面的148
========================程序如下======================================
package com.concurrenttest;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
/**
* @author 孟皓佶
* 验证volatile 变量可见性
* 验证volatile 并不保证同步操作变量
*/
public class VolatileTest {
private static Logger logger = Logger.getLogger(VolatileTest.class);
private static volatile int i;
private static int totalLoop=50;
private static int totalThread=3;
public static void main(String[] args) {
Runnable run = new Runnable(){
@Override
public void run() {
logger.info("线程"+Thread.currentThread().getName()+" 开始");
for(int j=0;j<totalLoop;j++){
/*这里对volatile 变量++
*i++不是原子操作,分为读取i值,再i=i+1
*线程有自己的运行内存空间
*volatile语义
* 内存屏障
* volatile语义1.线程各自的运行空间需要读取i的时候,都会从主存读取最新的值过来
* (这个时候对i的+1操作,其实是线程对自己内存的i+1
* volatile语义2 volatile的第二个作用是线程每次对volatile的变量的改变,在完成后,都会立即同步到主存
*
* !!volatile只能保证变量的变化在线程间立即可见,并不保证对共享变量操作的同步性
*/
i++;
//这里就同步到主存了
try {
//为了便于观察,sleep 下
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
logger.info("线程"+Thread.currentThread().getName()+" 结束");
}
};
ThreadGroup threadGroup = new ThreadGroup("concurrent");
Thread threadBad = new Thread(threadGroup,new Runnable(){
@Override
public void run() {
logger.info("线程 badBoy 开始");
int value = i;
//logger.info("线程 badBoy 读取到i="+value);
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
i=value+1;
logger.info("线程 badBoy 结束设置"+(value+1));
}
});
logger.info("========初始======"+i+"================");
logger.info("同时开始"+totalThread+"个线程,每个线程对volatile变量++"+totalLoop+"次");
//这里同时启动三个线程并发
for(int threadCount = 0;threadCount<totalThread;threadCount++){
Thread thread = new Thread(threadGroup,run);
thread.start();
}
threadBad.start();
waitFinish(threadGroup);
//等待所有线程结束,输出最后结果
logger.info("=======结束======="+i+"================");
}
private static void waitFinish(ThreadGroup threadGroup) {
while (threadGroup.activeCount() > 0) {
try {
TimeUnit.MICROSECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
====================运行结果=============================
2017-10-13 00:44:56,168 INFO main (com.concurrenttest.VolatileTest.main:71) - ========初始======0================
2017-10-13 00:44:56,170 INFO main (com.concurrenttest.VolatileTest.main:72) - 同时开始3个线程,每个线程对volatile变量++50次
2017-10-13 00:44:56,170 INFO Thread-1 (com.concurrenttest.VolatileTest.run:22) - 线程Thread-1 开始
2017-10-13 00:44:56,170 INFO Thread-3 (com.concurrenttest.VolatileTest.run:22) - 线程Thread-3 开始
2017-10-13 00:44:56,171 INFO Thread-0 (com.concurrenttest.VolatileTest.run:55) - 线程 badBoy 开始
2017-10-13 00:44:56,170 INFO Thread-2 (com.concurrenttest.VolatileTest.run:22) - 线程Thread-2 开始
2017-10-13 00:45:00,171 INFO Thread-0 (com.concurrenttest.VolatileTest.run:65) - 线程 badBoy 结束设置3
2017-10-13 00:45:01,171 INFO Thread-1 (com.concurrenttest.VolatileTest.run:45) - 线程Thread-1 结束
2017-10-13 00:45:01,171 INFO Thread-3 (com.concurrenttest.VolatileTest.run:45) - 线程Thread-3 结束
2017-10-13 00:45:01,174 INFO Thread-2 (com.concurrenttest.VolatileTest.run:45) - 线程Thread-2 结束
2017-10-13 00:45:01,175 INFO main (com.concurrenttest.VolatileTest.main:82) - =======结束=======32================
由此可见,volatile 只能保证可见性,无法保证同步。那怎么保证这种++ 不出问题呢,选择有
1. 干脆用synchronized, 据说现在做了优化,性能不差
2. 可以使用AtomicInteger, AtomicInteger运用CAS原理,实现了非阻塞的方案。
Atomic 运行结果
2017-10-13 00:55:18,094 INFO main (com.concurrenttest.AtomicIntegerTest.main:37) - ========初始======0================
2017-10-13 00:55:18,098 INFO main (com.concurrenttest.AtomicIntegerTest.main:38) -
同时开始3个线程,每个线程对volatile变量++500000次
2017-10-13 00:55:18,099 INFO Thread-0 (com.concurrenttest.AtomicIntegerTest.run:24) - 线程Thread-0 开始
2017-10-13 00:55:18,099 INFO Thread-1 (com.concurrenttest.AtomicIntegerTest.run:24) - 线程Thread-1 开始
2017-10-13 00:55:18,104 INFO Thread-2 (com.concurrenttest.AtomicIntegerTest.run:24) - 线程Thread-2 开始
2017-10-13 00:55:18,131 INFO Thread-1 (com.concurrenttest.AtomicIntegerTest.run:30) - 线程Thread-1 结束
2017-10-13 00:55:18,131 INFO Thread-0 (com.concurrenttest.AtomicIntegerTest.run:30) - 线程Thread-0 结束
2017-10-13 00:55:18,131 INFO Thread-2 (com.concurrenttest.AtomicIntegerTest.run:30) - 线程Thread-2 结束
2017-10-13 00:55:18,134 INFO main (com.concurrenttest.AtomicIntegerTest.main:47) - =======结束=======1500000================
以下是使用atomicinteger源代码
package com.concurrenttest;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.Logger;
/**
* @author 孟皓佶
* 验证volatile 变量可见性
* 验证volatile 并不保证同步操作变量
*/
public class AtomicIntegerTest {
private static Logger logger = Logger.getLogger(AtomicIntegerTest.class);
private static AtomicInteger i = new AtomicInteger(0);
private static int totalLoop=500000;
private static int totalThread=3;
public static void main(String[] args) {
Runnable run = new Runnable(){
@Override
public void run() {
logger.info("线程"+Thread.currentThread().getName()+" 开始");
for(int j=0;j<totalLoop;j++){
i.getAndIncrement();
}
logger.info("线程"+Thread.currentThread().getName()+" 结束");
}
};
ThreadGroup threadGroup = new ThreadGroup("concurrent");
logger.info("========初始======"+i.get()+"================");
logger.info("同时开始"+totalThread+"个线程,每个线程对volatile变量++"+totalLoop+"次");
//这里同时启动三个线程并发
for(int threadCount = 0;threadCount<totalThread;threadCount++){
Thread thread = new Thread(threadGroup,run);
thread.start();
}
waitFinish(threadGroup);
//等待所有线程结束,输出最后结果
logger.info("=======结束======="+i.get()+"================");
}
private static void waitFinish(ThreadGroup threadGroup) {
while (threadGroup.activeCount() > 0) {
try {
TimeUnit.MICROSECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}