【多线程与高并发】- 浅谈volatile

打印 上一主题 下一主题

主题 897|帖子 897|积分 2691

浅谈volatile



目录

简介

volatile是Java语言中的一种轻量级的同步机制,它可以确保共享变量的内存可见性,也就是当一个线程修改了共享变量的值时,其他线程能够立即知道这个修改。跟synchronized一样都是同步机制,但是相比之下,synchronized属于重量级锁,volatile属于轻量级锁。
JMM概述

JMM就是Java内存模型(Java Memory Model),是Java虚拟机规范的一种内存模型,屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果。
Java内存模型规定了Java程序的变量(包括实例变量,静态变量,但是不包括局部变量和方法参数)全部存储在主内存中,定义了各种变量(线程的共享变量)的访问规则,以及在JVM中将变量存储到主内存与从主内存读取变量的底层细节。
JMM的规定

  • 所有共享变量都存在于主内存(包括实例变量,静态变量,但是不包括局部变量和方法参数),因为局部变量是线程私有,不存在竞争问题。
  • 每个线程都有自己的工作内存,所需要的变量是主内存中的副本。
  • 线程对变量的读、写操作都只能在工作内存中完成,不能直接参与读写主内存的变量。
  • 不同的线程也不能去直接访问不同线程的工作内存的变量,线程间的变量传递需要通过主内存来中转完成。

volatile的特性

1、可见性

volatile可以保证线程的可见性,即当多个线程访问同一个变量的时候,此变量发生改变,其他线程也能实时获得到这个修改的值。
在java中,变量都会被放在推内存(所有线程共享的内存)中,多个线程对共享内存是不可见的,当每个线程去获取这个变量的值时,实际上是copy一份副本在线程自身的工作内存中。

举个例子

我们将main作为主线程,MyThread为子线程。在子线程中定义一个共享变量flag,主线程会去访问这个共享变量。在不加volatile的时候,flag在主线程读到的永远是为false,因为两个线程是不可见的。
  1. public class T2_Volatile01 {
  2.     public static void main(String[] args) { // 主线程
  3.         MyThread my = new MyThread();
  4.         my.start();
  5.         while (true) {
  6.             if (my.isFlag()) System.out.println("进入等待...");
  7.         }
  8.     }
  9. }
  10. class MyThread extends Thread { // 子线程
  11.     private volatile boolean flag = false;
  12.     @Override
  13.     public void run() {
  14.         try {
  15.             Thread.sleep(1000);
  16.         } catch (InterruptedException e) {
  17.             throw new RuntimeException(e);
  18.         }
  19.         flag = true;
  20.         System.out.println("flag 修改完毕!");
  21.     }
  22.     public boolean isFlag() {
  23.         return flag;
  24.     }
  25.     public void setFlag(boolean flag) {
  26.         this.flag = flag;
  27.     }
  28. }
复制代码
实际上是已经修改了的,只是线程读的都是自己的工作内存中的数据,然而,要解决这个问题,可以使用synchronized加锁和volatile修饰共享变量来解决,这两种都能让主线程拿到子线程修改的变量的值。
  1. synchronized (my) {
  2.     if (my.isFlag()) System.out.println("进入等待...");
  3. }
复制代码
加了synchronized锁,首先该线程会获得锁对象,接着会去清空工作内存,再从主内存中copy一份最新的值到工作变量中,接着执行代码, 打印输出,最后释放锁。

当然还能使用volatile关键字去修饰共享变量。一开始子线程从主内存中获取变量的副本到自己的工作内存,进行改值,此时还未写回主内存,主线程从主内存获取的变量的值也是一开始的初始值,等到子线程写回到主内存时,接下来其他线程的工作内存中此变量的副本将会失效,也就是类似于监听。在需要对此变量进行操作的时候,将会到主内存获取新的值保存到线程自身的工作内存中,从而确保了数据的一致。

总结

volatile能够保证不同线程对共享变量的可见性,也就是修改过的volatile修饰的共享变量只要被写回到主内存中,其他线程就能够马上看到最新的数据。
当一个线程对volatile修饰的变量进行写的操作时候,JMM会立即把该线程自身的工作内存的共享变量刷新到主内存中。
当对线程进行读操作的时候,JMM会立即把当前线程自身的工作内存设置无效,从而从主内存中去获取共享变量的数据。
2、无法保证原子性

原子性指的是一项操作要么都执行,要么都不执行,中途不允许中断也不受其他线程干扰。
举个例子

我们看以下案例代码,简单描述一下,AutoAccretion是一个线程类,里面定义了一个共享变量count,并去执行1万次的自增,在main线程中调用多线程去执行自增。我们所期望的结果是最终count的值是1000000,因为每个线程自增1万次,一共100个线程。
[code]public class T3_Volatile01 {    public static void main(String[] args) {        Runnable thread = new AutoAccretion();        for (int i = 1; i

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

慢吞云雾缓吐愁

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表