可以很明确地说,java中的volatile是不能保证线程安全的,根本原因在于它不能保证原子性。在此之前先了解几个概念。
为什么会出现线程安全问题
出现线程安全问题的根本原因还是和计算机的结构相关。
在计算机中,每条指令的运行是依靠cpu的,指令执行的过程中会涉及到临时数据的读取和写入,这些数据的读取和写入是发生在计算机主存中的。cpu的指令执行是很快的,但是发生在主存中的数据读取和写入却没有这么快,所以就出现了高速缓存的概念。
当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。
什么是原子性和可见性以及指令重排
原子性:,原子性是指执行某一个操作是不可分割的,如果某个操作是原子性的,那么就可
以说它是线程安全的,因为该操作只能由一个线程进行操作。
满足原子性操作的自然不存在线程安全问题。但不为原子性的变量操作就很容易引起线程安全问题.例如,t=t+1,这就不是一个原子操作,因为当某个线程执行这个指令的时候,首先这个线程会将t的值从主存中读取到高速缓存中,之后在高速缓存中+1完成之后再刷新到主存中。例如现在有线程A、线程B同时执行这一段指令,那么无论在高速缓存中是否有t的值,两个线程都必定会将t的值先读取到高速缓存,假设此时t为1,那么线程A将t加1,最后刷新到主存中的t就是2.线程B也是这么认为的,t为1,最后刷新到主存中t还是2.此时就出现了线程安全的问题
可见性:当某个线程修改了变量的值,其余的线程可以立刻知晓。
指令重排:在计算机中,指令重排的意思是操作系统未必会按照代码的顺序去执行每一条
指令。例如你的代码中有一段
a=a+1;
b=1;
假设第一条语句执行10ms,第二条语句执行1ms,此时操作系统就可能将顺序颠倒。
被volatile修饰的关键字可以保证变量的可见性以及禁止对此变量的指令重排序(关于这个变量的代码之前的代码必定在这个变量的代码之前执行,关于这个变量的代码之后的代码必定在这个变量的代码之后执行),但是volatile依旧不能保证线程安全问题,原因在于它不能保证作用于变量的代码是原子操作。
来看一段代码
public class VolatileProblem{
public static volatile int t = 0;
public static void main(String[] args){
testVolatile();
System.out.println(t);
}
public static void testVolatile(){
Thread[] threads = new Thread[10];
for(int i = 0; i < 10; i++){
//每个线程对t进行1000次加1的操作
threads[i] =new Thread(new Runnable(){
@Override
public void run(){
for(int j = 0; j < 1000; j++){
t = t + 1;
}
}
});
threads[i].start();
}
}
}
最终这个t的值是1000*10=10000吗,不是的,甚至差得离谱,发生这种现象得原因也很简单.t=t+1不是原子操作。可能某个线程刚读取完t的值去做别的了,此时有另外线程读取到得t的值恰好和它一样,此时就发生了线程安全问题。