自学内容网 自学内容网

并发编程volatile精解

多线程下变量的不可见性

在多线程并发执行的情况下,多个线程修改共享的成员变量,会出现一个线程修改了共享变量的值后,另一个线程不能直接看到该线程修改后的变量最新值。(多线程下修改共享变量会出现变量修改值后的不可见性)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可见性问题的原因
所有共享变量存在于主内存中,每个线程由自己的本地内存,而且线程读写共享数据也是通过本地内存交换的,所以才导致了可见性问题。
解决方法
1、加锁 2、使用volatile关键字(底层实现原理是内存屏障)【并发编程】volatile
在这里插入图片描述
在这里插入图片描述
volatile修改的变量可以在多线程并发修改下,实现线程间变量的可见性
在这里插入图片描述

在这里插入图片描述
在Java中,volatile 关键字确实不保证复合操作的原子性,但是对于long和double类型的变量,volatile 修饰的读写操作是原子的。
在这里插入图片描述
在多线程环境中,vulatile关键字可以保证共享数据的可见性,但是不能保证对数据操作的原子性(在多线程环境下volatile修饰的变量也是不安全的)
要保证数据的安全性,还需要使用锁机制。
在这里插入图片描述
什么是重排序
为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序。
在这里插入图片描述

好处
重排序可以提高处理的速度。
在这里插入图片描述
使用volatile可以禁止指令的重排序,从而修正重排序可能带来的并发安全问题。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(7)线程中断规则:对线程 interrupt 方法的调用,happens-before 被中断线程的代码检测到中断事件的发生。
(8)对象终结规则:一个对象的初始化完成,happens-before 它的 finalize() 方法的开始。

在这里插入图片描述

long和double的原子性

在java中,long和double都是8个字节共64位(一个字节=8bit),那么如果是一个32位的系统,读写long或double的 变量时会涉及到原子性问题,因为32位的系统要读完一个64位的变量,需要分两步执行,每次读取32位,这样就对double和long变量的赋值就会出现问题: 如果有两个线程同时写一个变量内存,一个进程写低32位,而另一个写高32位,这样将导致获取的64位数据是失效的数据。

public class LongTest09 implements Runnable{ 
   private volatile static long aLong = 0; 
   private volatile long value;
   public LongTest09(long value) { 
       this.setValue(value);
 }
   public long getValue() { 
       return value;
 }
   public void setValue(long value) { 
       this.value = value;
 }
   @Override
   public void run() { 
       int i = 0;
       while (i < 100000) {
           LongTest09.aLong = this.getValue(); 
           i++;
           // 赋值操作!!
           long temp = LongTest09.aLong; 
           // 取出值操作!!!
           if (temp != 1L && temp != -1L) {
               System.out.println("出现错误结果" + temp); 
               System.exit(0);
       } 
     }
       System.out.println("运行正确"); 
 }
   public static void main(String[] args) throws InterruptedException { 
       // 获取并打印当前JVM是32位还是64位的
       String sysNum = System.getProperty("sun.arch.data.model"); 
       System.out.println("系统的位数:"+sysNum);
       LongTest09 t1 = new LongTest09(1); 
       LongTest09 t2 = new LongTest09(-1); 
       Thread T1 = new Thread(t1);
       Thread T2 = new Thread(t2); 
       T1.start();
       T2.start(); 
       T1.join(); 
       T2.join(); 
 }
}

上面的代码在32位环境和64位环境执行的结果是不一样的: 32位环境:出现错误结果 原因:32位环境无法一次读取long类型数据,多线程环境下对Long变量的读写是不完整的,导致temp变量既不等于1也不等于-1。出现了long和double读写的原子性问题了。 64位环境:运行正确
如果是在64位的系统中,那么对64位的long和double的读写都是原子操作的。即可以以一次性读写long或double的整个64bit。如果在32位的JVM上,long和double就不是原子性操作了。

解决办法

  1. 对于64位的long和double,如果没有被volatile修饰,那么对其操作可以不是原子的。在操作的时候,可以分成两步,每次对32位操作。
  2. 如果使用volatile修饰long和double,那么其读写都是原子操作
  3. 在实现JVM时,可以自由选择是否把读写long和double作为原子操作;
  4. java中对于long和double类型的写操作不是原子操作,而是分成了两个32位的写操作。读操作是否也分成了两 个32位的读呢?在JSR-133之前的规范中,读也是分成了两个32位的读,但是从JSR-133规范开始,即JDK5开始,读操作也都具有原子性;
  5. java中对于其他类型的读写操作都是原子操作(除long和double类型以外);
  6. 对于引用类型的读写操作都是原子操作,无论引用类型的实际类型是32位的值还是64位的值;
  7. Java商业虚拟机已经解决了long和double的读写操作的原子性。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    双重检查锁
    双重检查锁(double checked locking)是对上述问题的一种优化。先判断对象是否已经被初始化,再决定要不要加锁。
public class Singleton {
//静态属性,volatile保证可见性和禁止指令的重排序
    private  volatile static Singleton uniqueSingleton;

    private Singleton() {
    }

    public Singleton getInstance() {
    //第一重检查锁定
        if (null == uniqueSingleton) {
        //同步代码块
            synchronized (Singleton.class) {
            //第二重检查锁定
                if (null == uniqueSingleton) {
                    //注意:非原子操作
                    uniqueSingleton = new Singleton();   // error
                }
            }
        }
        return uniqueSingleton;
    }
}
  • 检查变量是否被初始化(不去获得锁),如果已被初始化则立即返回。
  • 获取锁。
  • 再次检查变量是否已经被初始化,如果还没被初始化就初始化一个对象。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class UseVolatile1 implements Runnable { 
   volatile boolean flag = false;
   AtomicInteger realA = new AtomicInteger(); 
   @Override
   public void run() {
       for (int i = 0; i < 10000; i++) { 
           setDone();
           realA.incrementAndGet(); 
     }
 }
   private void setDone() {
       flag = true;   // 纯赋值操作符合预期 
       // flag = !flag ; // 这样做不符合预期 
 }
}
class Test{
    public static void main(String[] args) throws InterruptedException { 
       UseVolatile1 r =  new UseVolatile1();
       Thread thread1 = new Thread(r); 
       Thread thread2 = new Thread(r);
       thread1.start();
       thread2.start();
       thread1.join();
       thread2.join();
       System.out.println(r.flag);
       System.out.println(r.realA.get()); 
 }
}

在这里插入图片描述
触发器
按照volatile的可见性和禁止重排序以及happens-before规则,volatile可以作为刷新之前变量的触发器。我们可以将某个变量设置为volatile修饰,其他线程一旦发现该变量修改的值后,触发获取到的该变量之前的操作都将是最新的且可见。

public class VisibilityHP {
   int a = 1;
   int b = 2;
   int c = 3;
   volatile boolean flag = false; 
   private void write() {
       a = 3;
       b = 4 ;
       c = a;
       flag = true; 
 }
   private void read() {
       // flag被volatile修饰,充当了触发器,一旦值为true,此处立即对变量之前的操作可见。 
       while(flag){
             System.out.println("a=" + a + ";b=" + b +",c="+c ); 
     }
 }
   public static void main(String[] args) { 
       while (true) {
           VisibilityHP test = new VisibilityHP(); 
           new Thread(new Runnable() {
               @Override
               public void run() { 
                   try {
                       Thread.sleep(100);
                   } catch (InterruptedException e) { 
                       e.printStackTrace();
           }
                   test.write(); 
         }
           }).start();
           new Thread(new Runnable() { 
               @Override
               public void run() { 
                   try {
                       Thread.sleep(100);
                   } catch (InterruptedException e) { 
                       e.printStackTrace();
           }
                   test.read(); 
         }
         }).start();
      }
    }
 }

volatile可以作为刷新之前变量的触发器。我们可以将某个变量设置为volatile修饰,其他线程一旦发现该变量修改的 值后,触发获取到的该变量之前的操作都将是最新的且可见。

volatile与synchronized

在这里插入图片描述

volatile总结

在这里插入图片描述


原文地址:https://blog.csdn.net/w287586/article/details/143587774

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!