说说volatile

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();  
            }  
        }  

}


}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值