并发编程3个重要特性

并发编程的三个重要特性原子性、可见性、有序性

  • 原子性 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
  • 可见性 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
  • 有序性 即程序执行的顺序按照代码的先后顺序执行。 >java int a = 10; int b = 5; >

在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

ps:要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

volatile

volatile语义 - 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(保证变量的可见性) - 2)禁止进行指令重排序。(保证有序性)

为什么不保证原子性:volatile修饰的属性若再修改前被另一个线程读取了值,那么修改后,无法改变已经复制到工作内存中的值。

volatile static int a=0;
a++;

// 包含了2步操作:1、读取a。2、执行a+1 & 将a+1结果赋值给a

// 设:线程A、B同时执行以下语句,线程A执行完第1步后被挂起、线程B执行了a++,那么主存中a的值为1

// 但线程A的工作内存中还是0,由于线程A之前已读取了a的值 = 0,执行a++后再次将a的值刷新到主存 = 1

// 即 a++执行了2次,但2次都是从0变为1,故a的值最终为1

Java内存模型 通过上图可知: Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都必须在自己的工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。 Java内存模型只保证了基本读取和赋值是原子性操作。

为什么保证可见性: - volatile修饰的属性能够保证每次读取都是最新的值 - 但在多线程下不会也无法更新已经读取了的值

(1)对于普通的成员变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。 (2)当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

为什么保证有序性重排序时,以volatile修饰的属性的读/写操作代码为分界线(lock前缀指令),读/写操作前的代码不允许排到后面,后面不允许排到前面,由此保证有序性。

使用条件: 您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件: - 对变量的写操作不依赖于当前值。 - 该变量没有包含在具有其他变量的不变式中。

使用场景:

  • 模式 #1:状态标志

    volatile boolean shutdownRequested;
     
    ...
     
    public void shutdown() { 
    shutdownRequested = true; 
    }
     
    public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
    }
    
    
  • 模式 #2:双重检查锁单例模式

    public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
    }
    
    
  • 模式 #3:开销较低的“读-写锁”策略

     
    public class CheesyCounter {  
    // Employs the cheap read-write lock trick  
    // All mutative operations MUST be done with the 'this' lock held  
    private volatile int value;  
      
    //读操作,没有synchronized,提高性能  
    public int getValue() {   
        return value;   
    }   
      
    //写操作,必须synchronized。因为x++不是原子操作  
    public synchronized int increment() {  
        return value++;  
    } 
    }
    

总结

  • (1)对于普通成员变量它只保证了基本读取和赋值是原子性操作、它无法保证可见性、它会产生重排序。
  • (2)使用volatile关键字能保证可见性,和一定的有序性。而使用synchronized和Lock能保证原子性、可见性、有序性。

refer to: http://www.sohu.com/a/281007804_100212268 refer to: https://blog.csdn.net/vking_wang/article/details/9982709 refer to: https://www.runoob.com/design-pattern/singleton-pattern.html