volatile关键字
在讲解volatile关键字的时候,我们先看一个简单的例子:
package SynchronizedCass; public class t4 { public static void main(String[] args) { try { RunThread thread = new RunThread(); thread.start(); Thread.sleep(1000); thread.setRunning(false); System.out.println("已经被赋值为false"); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } } class RunThread extends Thread { private boolean isRunning = true; public boolean isRunning() { return isRunning; } public void setRunning(boolean isRunning) { this.isRunning = isRunning; } @Override public void run() { super.run(); System.out.println("进入run方法了"); while(isRunning == true) { //无限循环 } System.out.println("线程被停止了!"); } }
进入run方法了 已经被赋值为false
从结果上看:System.out.println("线程被停止了!"); 这句代码从未被执行,程序进入了死循环,这是为什么呢?
启动线程后 private boolean isRunning = true; 变量存在于内存的公共堆栈及线程的私有堆栈中。在JVM被设置为-server模式时为了线程运行的效率。线程一直在私有堆栈中取得isRunning的值是true;虽然代码thread.setRunning(false)被执行了,更新的却是公共堆栈中的isRunning变量值false,线程的私有堆栈中任为true,所以线程thread就一直处于死循环的状态。
这个问题就是公共堆栈中的值和私有堆栈中的值不同步造成的。那么如何解决这个问题,这就到了我们今天要介绍的关键字volatile,当他修饰isRunning变量时,强制性地从公共堆栈中进行取值。
package SynchronizedCass; public class t4 { public static void main(String[] args) { try { RunThread thread = new RunThread(); thread.start(); Thread.sleep(1000); thread.setRunning(false); System.out.println("已经被赋值为false"); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } } class RunThread extends Thread { volatile private boolean isRunning = true; public boolean isRunning() { return isRunning; } public void setRunning(boolean isRunning) { this.isRunning = isRunning; } @Override public void run() { super.run(); System.out.println("进入run方法了"); while(isRunning == true) { //无限循环 } System.out.println("线程被停止了!"); } }
进入run方法了 已经被赋值为false 线程被停止了!
我们看一下使用volatile关键字后发生了什么?
线程主体是不是强制地从公共内存中读取变量的值,它增加了实例变量在多个线程之间的可见性。
比较一下关键字volatile和synchronized,如下:
- 关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间的访问资源的同步性。
- 关键字volatile只能用于修饰变量,而synchronized可以修饰方法,以及代码块。
- 多线程访问volatile不会发生阻塞,而synchronized会发生阻塞。
- volatile能保证数据的可见性,但是不能保证原子性;而synchronized可以保证原子性,也间接保证可见性(因为他会将私有内存和公共内存中的数据做同步)
但是volatile关键字却有一个缺点,就是它是非原子的特性。简单看一下例子:
package SynchronizedCass; public class t5 { public static void main(String[] args) { MyThread[] mythreadArray = new MyThread[100]; for(int i = 0; i < 100; i++) { mythreadArray[i] = new MyThread(); } for(int i = 0; i < 100; i++) { mythreadArray[i].start(); } } } class MyThread extends Thread { volatile public static int count; private static void addCount() { for(int i = 0; i < 100; i++) { count++; //++操作分为三步,而volatile无法保证原子性,只能解决变量读取时的可见性 } System.out.println("count = " + count); } @Override public void run() { // TODO 自动生成的方法存根 super.run(); addCount(); } }
count = 4729
count = 4867
count = 5129
count = 5062
count = 5429
count = 4929
count = 5629
count = 5529
count = 5829
count = 5329
count = 5229
count = 6129
count = 6029
count = 5929
count = 5729
count = 6429
count = 6529
count = 6329
count = 6229
count = 6729
count = 6629
count = 6829
count = 6929
count = 7029
count = 7129
count = 7229
count = 7329
count = 7429
count = 7529
count = 7629
count = 7729
count = 7829
count = 7929
count = 8029
count = 8129
count = 8229
count = 8329
count = 8429
count = 8529
count = 8629
count = 8729
count = 8829
count = 8929
count = 9029
count = 9129
count = 9329
count = 9229
count = 9429
count = 9529
count = 9629
count = 9929
count = 9829
count = 9729
这个最终的count值和我们预想的100×100 =10000不一样,而且在打印结果的过程中出现了不同步的现象。分析一下原因:
volatile关键字保证多线程读取共享变量时可以获取最新值使用,但是却是非原子的,当我们修改实例变量的数据,如count++,该操作步骤分解如下:
- 从内存中读取count的值;
- 计算count的值
- 将count的值写入到内存中去
我们用图来看一下使用volatile时,出现非线程安全的原因。
- read和load阶段,从主存赋值变量到当前线程的工作内存中;
- use和assign阶段,执行代码,改变共享变量值;
- store和write阶段:用工作内存数据刷新主存对应变量的值;
当read和load之后,如果主存count变量发生修改,线程工作内存中的值由于已经加载,不会产生对应的变化,造成私有内存和公共内存中的变量不同步,出现了非线程安全的问题。
用volatile关键字,只能保证从主内存中加载到线程工作内存中的值是最新的,解决读取变量时的可见性问题,但是无法保证原子性,需要用synchronized加锁同步。
使用原子类i++进行操作
在进行i++操作的时候,可以用AtomicInteger原子类进行实现。
package SynchronizedCass; import java.util.concurrent.atomic.AtomicInteger; public class t5 { public static void main(String[] args) { AddCountThread countService = new AddCountThread(); Thread t1 = new Thread(countService); t1.start(); Thread t2 = new Thread(countService); t2.start(); Thread t3 = new Thread(countService); t3.start(); Thread t4 = new Thread(countService); t4.start(); Thread t5 = new Thread(countService); t5.start(); } } class AddCountThread extends Thread { private AtomicInteger count = new AtomicInteger(); @Override public void run() { for(int i = 0; i < 10000; i++) { System.out.println(count.incrementAndGet()); } } }
49993 49994 49995 49996 49997 49998 49999 50000
count是一个原子类,其incrementAndGet是以原子方式将当前值加 1。可以保证原子性。
但是原子也并不完全安全,当方法和方法之间的调用不是原子的时候,就必须用同步解决。
synchronized代码块具有可见性
synchronized包含两个特性:互斥性和可见性==========>“外练互斥,内修可见”
同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个所保护之前的所有的修改效果。
作者:Ryanjie
出处:http://www.cnblogs.com/ryanjan/
本文版权归作者和博客园所有,欢迎转载。转载请在留言板处留言给我,且在文章标明原文链接,谢谢!
如果您觉得本篇博文对您有所收获,觉得我还算用心,请点击右下角的 [推荐],谢谢!