【多线程】volatile关键字

一、volatile概念:

       关键字volatile可以说是java虚拟机提供的最轻量级的同步机制,但是它并不容易完全被正确、完整地理解。遇到需要处理多线程数据竞争问题的时候一律使用synchronized来进行同步。

       volatile关键字的主要作用是使变量在多个线程间可见。多个线程间可见指的是当一条线程修改了这个变量的值,会立即通知到其他的线程

二、内存模型原子性、可见性、有序性

       1、原子性

       原子是世界上最小的单位,具有不可分割性。比如i=0(i 非long和double类型)这个操作是不可分割的,那么我们认为这个;操作是原子操作。在比如i++;这个操作实际是a=a+1;是可分割的,所以它不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,如AtomicInteger、AtomicLong等。

        2、可见性

         可见性,是指线程之间的可见性,当一个线程操作了被volatile修饰的共享变量,其他线程能够立即得知这个修改。而普通变量并不能做到这一点,普通变量的值在线程间传递均需要通过主内存来完成。例如线程A修改了一个普通变量的值,然后向主内存进行回写,另一个线程B在线程A会写完成之后再从主内存进行读取操作,新变量值才会对线程B可见。也就是说会存在“工作内存与主内存同步延迟的线程”。

比如用volatile修饰的变量,就具有可见性,但不具有原子性。

        3、有序性

        Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行的语义”,后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。

        Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字禁止指令重排序,而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的,这条规则决定了持有同一个锁的两个同步块只能串行地进入。

三、示例

       

package volatileTest;
public class RunThread extends Thread{

	/*volatile*/
	private  boolean isRunning = true;
	private void setRunning(boolean isRunning){
		this.isRunning = isRunning;
	}
	
	public void run(){
		System.out.println("进入run方法..");
		int i = 0;
		while(isRunning == true){
			//一直空轮询
		}
		System.out.println("线程停止");
	}
	
	public static void main(String[] args) throws InterruptedException {
		RunThread rt = new RunThread();
		rt.start();
		Thread.sleep(1000);
		rt.setRunning(false);
		System.out.println("isRunning的值已经被设置了false");
		Thread.sleep(1000);
		System.out.println(rt.isRunning);
	}
      run方法中判断isRunning是否等于true,如果等于true,一直空轮询着,如果等于false,线程停止。输出结果如下:


      System.out.println(rt.isRunning);已经变为false;线程陷入了死循环(右上角红色正方形红色正方形一直存在),代码System.out.println("线程停止");从未被执行。这是为何?

      分析:

       在启动RunThread线程时,变量private boolean isRunning =true;存在于公共堆栈及线程的私有堆栈中。当JVM为server模式时为了线程运行的效率,线程一直在私有堆栈中取得isRunning的值是true。而代码rt.setRunning(false)虽然被执行,更新的却是公共堆栈中的isRunning变量值false,所以一直处于死循环的状态。内存结构如下图所示:

                                        

         这个问题其实就是私有堆栈中的值和公共堆栈中的值不同步造成的。解决这样问题就要使用volatile关键字了,它的主要作用是当线程访问isRunning这个变量时,强制从公共堆栈中进行读取。

          isRunning变量被volatile关键字修饰之后再次运行程序,结果如下:

          

不再出现死循环的情况,线程停止。此时的内存结果如下图所示

             

     使用volatile关键字增加了实例变量在多个线程之间的可见性。(在这个例子中存在两个线程一个main主线程,一个RunThread线程)。但volatile关键字最致命的缺点是不支持原子性。


四、关键词synchronized和volatile对比

       1、关键字volatile是线程同步的轻量级实现,所以volatile性能比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法以及普通代码块。

         2、多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。

         3、volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。

         4、关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

         线程安全包含原子性和可见性两个方面,Java的同步机制都是围绕这连个方面来确保线程安全的。

五、关键字volatile和ThreadLocal对比

      比如成员变量a默认为0,如果没有volatile关键字修饰变量a,当线程1把a set为1,其他线程get(a)之后的结果仍为0。当使用volatile修饰a之后,线程1把a set为1,其他线程get(a)之后的结果为 1,这是因为volatile关键字保证了线程的可见性。当使用ThreadLocal,线程1把a set为1,线程2把a set为2。线程1 get(a)为1.线程2 get(a)为2.这是因为ThreadLocal保证了线程隔离性







      






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值