多线程编程-volatile关键字(三)
2.3 volatile关键字
Volatile关键字的主要作用是使变量的多个线程间可见。还有一点就是禁止指令重排序。
使用volatile关键字增加了实例变量在多个线程之间的可见性,但是volatile不能保证原子性。
关键synchronized和volatile的比较:
1)volatile是线程同步的轻量级实现,所以volatile性能比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法,代码块。
2)多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
3)Volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为他会将线程的私有内存和公共内存中的数据做同步。
4)Volatile解决的是变量在多个线程之间的可见性;synchronized解决的是多个线程之间资源访问的同步性。
2.3.1 volatile非原子特性
测试代码:
public class Thread1 extends Thread{
public static int count =0;
private static void addCount(){
int index =0;
while(index <100){
count++;
index++;
}
System.out.println("count = "+count);
}
@Override
public void run(){
addCount();
}
}
public class RunDemo {
public static void main(String[] args) {
Thread1[] t1 = new Thread1[100];
for(int i=0; i<100; i++){
t1[i]= new Thread1();
}
for(int i=0; i<100; i++){
t1[i].start();
}
}
}
运行结果:
……
count = 9714
count = 9814
count = 9414
count = 9914
添加volatile关键字: public static int count=0;并不能解决这个问题,推荐的做法是使用synchronized关键字:synchronized privatestatic void addCount(){…},这种情况下volatile用不用都无所谓。
关键字volatile提示线程每次从共享内存读取变量,而不是从线程的工作内存读取,这保证了同步数据的可见性。但是这里的count++并不是一个原子操作,也就是非线程安全的。Count++可以分解为如下步骤:
1) 从内存取出count值
2)计算count值(count = count+1)
3)将count值写入内存
在第二步时,如果其他线程也修改了count的值,就会出现脏读,解决这个问题的办法就是synchronized关键字。
下图展示了使用volatile时出现非线程安全的原因:
1) read和load阶段:从主存复制变量到线程工作内存;
2) use 和assign阶段;执行代码,改变共享变量值;
3) store 和write阶段:用线程工作内存数据刷新主存对应变量的值。
多线程环境下,use和assign是多次出现的,但这一操作不是原子性的,也即是在read和load后,如果主存count变量被修改了,线程工作内存中的值由于已经加载了,不会产生对应的变化,也就导致了工作内存和主存变量的不同步,出现了非线程安全。
Volatile修饰的变量,虚拟机只是保证从主存加载到线程工作内存的值是最新的,解决的是变量读时的可见性问题,无法保证操作的原子性,所以对于多线程访问同一个实例变量还要加锁同步。
2.3.2 使用原子类进行i++操作除了使用synchronized实现同步外,还可以使用AtomicInteger原子类实现。
原子操作是不能分割的整体,没有其他线程能够中断或检查正在原子操作的变量。一个原子(atomic)类型就是一个原子操作可用的类型,他可以在没有锁的情况下做到线程安全。
public class Thread1 extends Thread{
//volatile public static int count =0;
private static AtomicInteger count = new AtomicInteger(0);
//synchronized
private static void addCount(){
int index =0;
while(index <100){
//count++;
count.incrementAndGet();
index++;
}
System.out.println("count = "+count);
}
@Override
public void run(){
addCount();
}
}
也可以得到期望结果。
上一篇: 第三方支付-java 返回success,带双引号的解决方法-支付流程
下一篇: 职责链模式