zoukankan      html  css  js  c++  java
  • 【慕课网学习笔记】Java共享变量的可见性和原子性

    1. Java内存模型(Java Memory Model, JMM)

    Java的内存模型如下,所有变量都存储在主内存中,每个线程都有自己的工作内存。

    共享变量:如果一个变量在多个线程中都使用到了,那么这个变量就是这几个线程的共享变量。

    可见性:一个线程对共享变量的修改,能够及时地到主内存并且让其他的线程看到。

    怎么理解上面的可见性的意思呢?

    线程对共享变量的修改,只能在自己的工作内存里操作,不能直接对主内存中的共享变量进行修改。而且一个线程不能直接访问另一个线程中的变量的值,只能通过主内存进行共享传递。

    那么就要求线程A对共享变量修改后,及时地更新到主内存中,线程B才可以及时地从主内存获取最新的值到工作内存。

    比如一个共享变量int i = 0; 线程A将其改为i =1; 其他线程此时获取i的值,应该能及时地得到1,而不是0。

    2. synchronized实现可见性

    synchronized除了常见的原子性,还实现了可见性。这是因为:

    1) 线程解锁前,必须把共享变量的最新值刷新到主内存中去;

    2) 线程加锁时,将清空工作内存中的共享变量的值,使用到共享变量时,从主内存中获取最新的共享变量值(加锁和解锁需要同一把锁)

    3. volatile实现可见性

    通过内存屏障和禁止重排序优化来实现可见性。

    1) 对共享变量进行写操作后,加入一条store屏障指令,强制将共享变量的值刷新到主内存;

    2) 对共享变量进行读操作前,加入一条load屏障指令,强制从主内存中将最新值刷新到工作内存;

    4.volatile不能保证原子性

    一个比较典型的例子是++运算符。

    在下面的代码中,一共创建了1000个线程,预期应该是加了1000次,那么number的值应该是1000,实际上有可能并不是。

    这是因为,++运算符并不是一次操作。以number++为例,可以看作是,先从主内存中取出number的值,然后将其加1,刷新工作内存,刷新主内存,这么几个步骤。

    而volatile并不能保证原子性,这就意味着,有可能出现这种情况:

    1)线程A获取到主内存的number的值(假设为10)到工作内存

    2)此时CPU调度,A暂停,线程B开始执行,同样从主内存中获取到number为10,number++后,number为11,刷新到主内存

    3)线程A继续执行number++,它的工作内存中number为10,执行完毕刷新到主内存,此时,number的值为11. 也就是说,AB两个线程同时进行了+1操作,但最终的结果,只加了1

     1 public class VolatileDemo {
     2 
     3     private int number = 0;
     4 
     5     public void increase() {
     6         number++;
     7     }
     8 
     9     public int getNumber() {
    10         return number;
    11     }
    12 
    13     public static void main(String[] args) {
    14         final VolatileDemo demo = new VolatileDemo();
    15 
    16         for (int i = 0; i <= 999; i++) {
    17             new Thread(new Runnable() {
    18                 @Override
    19                 public void run() {
    20                     demo.increase();
    21                 }
    22             }).start();
    23         }
    24         
    25         //线程未执行完,主线程让出CPU资源
    26         while(Thread.activeCount() > 1){
    27             Thread.yield();
    28         }
    29 
    30         //待上面的线程都执行完了,再打印,避免打印的不是最后的数据
    31         System.out.println(demo.getNumber());
    32     }
    33 }

    5.volatile适用场景

    1)对共享变量的写操作,不依赖于其之前的值

    不合适:number++, number = number * 2, number += 1等

    合适:boolean值

    2)该变量没有包含在具有其他变量的不变式中,也就是说,不同的volatile变量之间,不能互相依赖

    6.AtomicInteger实现递增

    上面我们已经知道一个整型的共享变量要实现递增,如果使用++运算符,即使加上volatile关键字,也是无法保证其原子性的。而如果在访问变量时加上synchronized块,或者可重入锁,开销又太大。

    JDK1.5之后,可以使用AtomicInteger进行递增。该类是线程安全的。

    将上面的代码修改如下,就可以保证原子性和可见性。

     1 import java.util.concurrent.atomic.AtomicInteger;
     2 
     3 public class VolatileDemo {
     4 
     5     private AtomicInteger number = new AtomicInteger(0);
     6 
     7     public void increase() {
     8         number.incrementAndGet();
     9     }
    10 
    11     public int getNumber() {
    12         return number.intValue();
    13     }
    14 
    15     public static void main(String[] args) {
    16         final VolatileDemo demo = new VolatileDemo();
    17 
    18         for (int i = 0; i <= 999; i++) {
    19             new Thread(new Runnable() {
    20                 @Override
    21                 public void run() {
    22                     demo.increase();
    23                 }
    24             }).start();
    25         }
    26         
    27         //线程未执行完,主线程让出CPU资源
    28         while(Thread.activeCount() > 1){
    29             Thread.yield();
    30         }
    31 
    32         //待上面的线程都执行完了,再打印,避免打印的不是最后的数据
    33         System.out.println(demo.getNumber());
    34     }
    35 }
  • 相关阅读:
    【nyoj-1274】信道安全(SPFA)
    【lightoj-1002】Country Roads(dijkstra变形)
    【牛客练习赛12-B】迷宫(BFS)
    【数论-逆元】复习总结
    【hdu1705】Count the grid(皮克定理)
    CSS 清除默认样式
    vue2.0项目实战使用axios发送请求
    axios基本用法
    vue2.0使用Sortable.js实现的拖拽功能
    vue2.0s中eventBus实现兄弟组件通信
  • 原文地址:https://www.cnblogs.com/kingsleylam/p/6014441.html
Copyright © 2011-2022 走看看