2020年11月21日星期六

volatile

JMM(java内存模型)

  • JMM屏蔽了底层不同计算机的区别,描述了Java程序中线程共享变量的访问规则,以及在jvm中将变量存储到内存和从内存中读取变量这样的底层细节。

  • JMM有以下规定:

    • 所有的共享变量都存储与主内存中,这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。

    • 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。

    • 线程对变量的所有操作(读和写)都必须在工作内存中完成,而不能直接读写主内存中的变量。

    • 不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值传递需要通过主内存中转来完成。

多线程下变量的不可见性:

public class test7 { public static void main(String[] args) {  MyThread t = new MyThread();  t.start();  while (true) {   if (t.isFlag()) {    System.out.println("停不下来了"); // 不会执行到这里   }  } }}class MyThread extends Thread { private boolean flag = false; // private volatile boolean flag = false; @Override public void run() {  try {   Thread.sleep(2000);  } catch (InterruptedException e) {   e.printStackTrace();  }  flag = true;  System.out.println("flag被修改了"); } public boolean isFlag() {  return flag; }}

原因:

  • 子线程t从主内存读取到数据放入其对应的工作内存
  • 将flag的值更改为true,但flag的值还没有写回主内存
  • 此时main方法读取到了flag的值为false
  • 当子线程t将flag的值写回主内存后,主线程没有再去读取主内存中的值,所以while(true)读取到的值一直是false。

volatile 的特性

  • volite 可以实现并发下共享变量的可见性;

  • volite 不保证原子性;

  • volite 可以防止指令重排序的操作。

    使用原子类来保证原子性:

    public AtomicInteger(): 初始化一个默认值为0的原子型Integer public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer int get(): 获取值 int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。 int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。 int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的 value)相加,并返回结果。 int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值
    private static AtomicInteger atomicInteger = new AtomicInteger();Runnable r = () -> {  for (int i = 0; i < 100; i++) {   atomicInteger.incrementAndGet();  }};

    有时为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序。重排序可以提高处理的速度。

volatile写读建立的happens-before关系

happens-before :前一个操作的结果可以被后续的操作获取。

happens-before规则:

  1. 程序顺序规则(单线程规则)

    同一个线程中前面的所有写操作对后面的操作可见

  2. 锁规则(Synchronized,Lock等)

    如果线程1解锁了monitor a,接着线程2锁定了a,那么,线程1解锁a之前的写操作都对线程2可见(线程

    1和线程2可以是同一个线程)

  3. volatile变量规则:

    如果线程1写入了volatile变量v(临界资源),接着线程2读取了v,那么,线程1写入v及之前的写操作都

    对线程2可见(线程1和线程2可以是同一个线程)

  4. 传递性

    A h-b B , B h-b C 那么可以得到 A h-b C

  5. join()规则:

    线程t1写入的所有变量,在任意其它线程t2调用t1.join(),或者t1.isAlive() 成功返回后,都对t2可见。

  6. start()规则:

    假定线程A在执行过程中,通过执行ThreadB.start()来启动线程B,那么线程A对共享变量的修改在接下来

    线程B开始执行前对线程B可见。注意:线程B启动之后,线程A在对变量修改线程B未必可见。

public class VisibilityHP {  int a = 1;  int b = 2;  private void write() {   a = 3;  b = a;  } private void read() {   System.out.println("b=" + b + ";a=" + a);  } public static void main(String[] args) {   while (true) {    VisibilityHP test = new VisibilityHP();    new Thread(new Runnable() {     @Override     public void run() {      test.write();     }    }).start();    new Thread(new Runnable() {     @Override     public void run() {      test.read();     }    }).start();   }  } }

没给b加volatile,那么有可能出现a=1 , b = 3 。因为a虽然被修改了,但是其他线程不可见,而b恰好其他线程可见,造成了b=3 , a=1。

如果使用volatile修饰long和double,那么其读写都是原子操作

volatile在双重检查加锁的单例中的应用

饿汉式(静态常量)

public class Singleton01 { private static final Singleton01 Intance = new Singleton01(); private Singleton01() {} public static Singleton01 getIntance() {  return Intance; }}

饿汉式(静态代码块)

public class Singleton02 { private final static Singleton02 Intance; static {  Intance = new Singleton02(); } private Singleton02() {} public static Singleton02 getInstance() {  return Intance; }}

懒汉式(线程安全,性能差)

public class Singleton03 { private static Singleton03 Instance; private Singleton03() {} public static synchronized Singleton03 getInstance() {  if (Instance == null) {   Instance = new Singleton03();  }  return Instance; }}

懒汉式(volatile双重检查模式,推荐)

public class Singleton04 { private static volatile Singleton04 Instance = null; private Singleton04() {} public static Singleton04 getInstance() {  if (Instance == null) {   synchronized (Singleton04.class) {    if (Instance == null) {     //创建对象的过程是非原子操作     Instance = new Singleton04();    }   }  }  return Instance; }}

此处加上volatile 的作用:

① 禁止指令重排序。

创建对象的过程要经过以下几个步骤s:

a. 分配内存空间

b. 调用构造器,初始化实例

c. 返回地址给引用

原因:由于创建对象是一个非原子操作,编译器可能会重排序,即只是在内存中开辟一片存储空间后直接返回内存的引用。而下一个线程在判断 instance 时就不为null 了,但此时该线程只是拿到了没有初始化完成的对象,该线程可能会继续拿着这个没有初始化的对象继续进行操作,容易触发"NPE 异常"。

② 保证可见性

静态内部类单例方式

public class Singleton05 { private Singleton05() {} private static class SingletonInstance {  private static final Singleton05 INSTANCE = new Singleton05(); }  public static Singleton05 getInstance() {  return SingletonInstance.INSTANCE; }}
  1. 静态内部类只有在调用时才会被加载,jvm在底层会保证只有一个线程去初始化实例,下一个线程获取实例时就直接返回。
  2. 相比于双重检查,静态内部类的代码更简洁。但基于volatile的双重检查有一个额外的优势:除了可以对静态字段实现延迟加载初始化外,还可以对实例字段实现延迟初始化。

volatile使用场景

  1. volatile适合做多线程中的纯赋值操作:如果一个共享变量自始至终只被各个线程赋值,而没有其他操作,那么可以用volatile来代替synchronized,因为赋值操作本身是原子性的,而volatile又保证了可见性,所以足以保证线程安全。

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

    public class test8 { int a = 1; int b = 2; int c = 3; volatile boolean flag = false; public void write() {  a = 100;  b = 200;  c = 300;  flag = true; } public void read() {  while (flag) {   System.out.println("a=" + a + " " + "b=" + b + " " + "c=" + c);   break;  } } public static void main(String[] args) {  test8 test8 = new test8();  new Thread(() -> {   test8.write();  }).start();  new Thread(() -> {   test8.read();  }).start(); }}

volatile 和synchronized的区别

  1. volatile只能修饰实例变量和类变量,而synchronized可以修饰方法,以及代码块。
  2. volatile保证数据的可见性,但是不保证原子性,不保证线程安全。
  3. volatile可以禁止指令重排序,解决单例双重检查对象初始化代码执行乱序问题。
  4. volatile可以看做轻量版synchronized,volatile不保证原子性,但是如果对一个共享变量只进行纯赋值操作,而没有其他操作,那么可以使用volatile来代替synchronized,因为赋值本身是有原子性的,而volatile又保证了可见性,所以就保证了线程安全。








原文转载:http://www.shaoqun.com/a/492758.html

prime day:https://www.ikjzd.com/w/131.html

智赢:https://www.ikjzd.com/w/1511

启明星软件:https://www.ikjzd.com/w/1436


JMM(java内存模型)JMM屏蔽了底层不同计算机的区别,描述了Java程序中线程共享变量的访问规则,以及在jvm中将变量存储到内存和从内存中读取变量这样的底层细节。JMM有以下规定:所有的共享变量都存储与主内存中,这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。
gtin:gtin
洋老板:洋老板
【肯尼亚旅游胜地】--肯尼亚旅游胜地怎么样:【肯尼亚旅游胜地】--肯尼亚旅游胜地怎么样
亚洲湾水上乐园需要注意什么?博鳌亚洲湾水上乐园注意事项?:亚洲湾水上乐园需要注意什么?博鳌亚洲湾水上乐园注意事项?
"神秘魔力盛宴"方特魔法节即将揭幕 :"神秘魔力盛宴"方特魔法节即将揭幕

没有评论:

发表评论

跨境电商资讯:外贸宣传平台有哪些(出口的

现在很多做外贸的人都非常关注 外贸企业怎么推广 ,而现在推广的途径和平台有很多,企业如果都做,成本和时间精力是一个问题,而且并不是所有的推广渠道都是有用的。今天云程网络就来为大家盘点几个有效的外贸推广渠道。 一、海外社交媒体营销 Facebook,领英等海外社交媒体营销在近几年得...