简介

volatile是Java提供的一种轻量级的同步机制。Java 语言蕴含两种外在的同步机制:同步块(或办法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。然而volatile 变量的同步性较差(有时它更简略并且开销更低),而且其应用也更容易出错。

Java volatile关键字用于将Java变量标记为“存储在主存储器中”。更确切地说,这意味着,每次读取一个volatile变量都将从计算机的主内存中读取,而不是从CPU缓存中读取,并且每次写入volatile变量都将写入主内存,而不仅仅是CPU缓存。

实际上,自Java 5以来,volatile关键字保障的不仅仅是向主存储器写入和读取volatile变量。我将在以下局部解释。

个性

能够把对volatile变量的单个读/写,看成是应用同一个锁对这些单个读/写操作做了同步

当咱们申明共享变量为volatile后,对这个变量的读/写将会很特地。了解volatile个性的一个好办法是:把对volatile变量的单个读/写,看成是应用同一个锁对这些单个读/写操作做了同步。

COPYclass VolatileFeaturesExample {    //应用volatile申明64位的long型变量    volatile long vl = 0L;    public void set(long l) {        vl = l;   //单个volatile变量的写    }    public void getAndIncrement () {        vl++;    //复合(多个)volatile变量的读/写    }    public long get() {        return vl;   //单个volatile变量的读    }}
假如有多个线程别离调用下面程序的三个办法,这个程序在语义上和上面程序等价:
COPYclass VolatileFeaturesExample {    long vl = 0L;               // 64位的long型一般变量    //对单个的一般 变量的写用同一个锁同步    public synchronized void set(long l) {                    vl = l;    }    public void getAndIncrement () { //一般办法调用        long temp = get();           //调用已同步的读办法        temp += 1L;                  //一般写操作        set(temp);                   //调用已同步的写办法    }    public synchronized long get() {         //对单个的一般变量的读用同一个锁同步        return vl;    }}

如下面示例程序所示,对一个volatile变量的单个读/写操作,与对一个一般变量的读/写操作应用同一个锁来同步,它们之间的执行成果雷同。

锁的happens-before规定保障开释锁和获取锁的两个线程之间的内存可见性,这意味着对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最初的写入。

锁的语义决定了临界区代码的执行具备原子性。这意味着即便是64位的long型和double型变量,只有它是volatile变量,对该变量的读写就将具备原子性。如果是多个volatile操作或相似于volatile++这种复合操作,这些操作整体上不具备原子性。

简而言之,volatile变量本身具备下列个性:

原子性

即一个操作或者多个操作 要么全副执行并且执行的过程不会被任何因素打断,要么就都不执行。

原子性是回绝多线程操作的,不论是多核还是单核,具备原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。例如 a=1是原子性操作,然而a++和a +=1就不是原子性操作。Java中的原子性操作包含:

  • 根本类型的读取和赋值操作,且赋值必须是数字赋值给变量,变量之间的互相赋值不是原子性操作。
  • 所有援用reference的赋值操作
  • java.concurrent.Atomic.* 包中所有类的所有操作

可见性

指当多个线程拜访同一个变量时,一个线程批改了这个变量的值,其余线程可能立刻看失去批改的值。

在多线程环境下,一个线程对共享变量的操作对其余线程是不可见的。Java提供了volatile来保障可见性,当一个变量被volatile润饰后,示意着线程本地内存有效,当一个线程批改共享变量后他会立刻被更新到主内存中,其余线程读取共享变量时,会间接从主内存中读取。当然,synchronize和Lock都能够保障可见性。synchronized和Lock能保障同一时刻只有一个线程获取锁而后执行同步代码,并且在开释锁之前会将对变量的批改刷新到主存当中。因而能够保障可见性。

在线程应用非volatile变量的多线程应用程序中,出于性能起因,每个线程能够在解决它们时将变量从主存储器拷贝到CPU高速缓存中。如果您的计算机蕴含多个CPU,则每个线程能够在不同的CPU上运行。这意味着,每个线程都能够将变量复制到不同CPU的CPU缓存中。这在这里阐明:

对于volatile变量,无奈保障Java虚拟机(JVM)何时将数据从主内存读取到CPU缓存中,或将数据从CPU缓存写入主内存。这可能会导致一些问题,我将在以下局部中解释。

设想一下两个或多个线程能够访问共享对象的状况,该共享对象蕴含一个申明如下的计数器变量:

COPYpublic class SharedObject {    public int counter = 0;}
再设想一下,只有线程1对counter变量进行减少操作,但线程1和线程2都可能读取变量counter

如果counter变量未声明volatile,则无奈保障何时将counter变量的值从CPU缓存写回主存储器。这意味着,CPU高速缓存中的counter变量值可能与主存储器中的变量值不同。这种状况如下所示:

线程没有看到变量的最新值的问题,是因为它还没有被另一个线程写回主内存,这被称为“可见性”问题,其余线程看不到一个线程的某些更新。

volatile可见性保障
Java volatile关键字旨在解决变量可见性问题。通过应用volatile申明counter变量,对变量counter的所有写操作都将立刻写回主存储器。此外,counter变量的所有读取都将间接从主存储器中读取。

上面是counter变量申明为volatile的样子:

COPYpublic class SharedObject {    public volatile int counter = 0;}
申明变量为volatile,对其余线程写入该变量 保障了可见性

在下面给出的场景中,一个线程(T1)批改计数器,另一个线程(T2)读取计数器(但从不批改它),申明该counter变量为volatile足以保障写入counter变量对T2的可见性。

然而,如果T1和T2都在减少counter变量,那么申明counter变量为volatile就不够了。稍后会具体介绍。

齐全volatile可见性保障
实际上,Java volatile的可见性保障超出了volatile变量自身。可见性保障如下:
  • 如果线程A写入volatile变量并且线程B随后读取这个volatile变量,则在写入volatile变量之前对线程A可见的所有变量在线程B读取volatile变量后也将对线程B可见。
  • 如果线程A读取volatile变量,则读取volatile变量时对线程A可见的所有变量也将从主存储器从新读取。

让我用代码示例阐明:

COPYpublic class MyClass {    private int years;    private int months    private volatile int days;    public void update(int years, int months, int days){        this.years  = years;        this.months = months;        this.days   = days;    }}
udpate()办法写入三个变量,其中只有days是volatile变量。

齐全volatile可见性保障意味着,当将一个值写入days时,对线程可见的其余所有变量也会写入主存储器。这意味着,当一个值被写入daysyearsmonths的值也被写入主存储器(留神days的写入在最初)。

当读取yearsmonthsdays的值你能够这样做:
COPYpublic class MyClass {    private int years;    private int months    private volatile int days;    public int totalDays() {        int total = this.days;        total += months * 30;        total += years * 365;        return total;    }    public void update(int years, int months, int days){        this.years  = years;        this.months = months;        this.days   = days;    }}

留神totalDays()办法通过读取days的值到total变量中开始。当读取days的值时,后续monthsyears值的读取也会从主存储器中读取。因而应用上述读取序列能够保障看到最新的daysmonthsyears值。

有序性

即程序执行的程序依照代码的先后顺序执行。

java内存模型中的有序性能够总结为:如果在本线程内察看,所有操作都是有序的;如果在一个线程中察看另一个线程,所有操作都是无序的。前半句是指“线程内体现为串行语义”,后半句是指“指令重排序”景象和“工作内存主主内存同步提早”景象。
在Java内存模型中,为了效率是容许编译器和处理器对指令进行重排序,当然重排序不会影响单线程的运行后果,然而对多线程会有影响。Java提供volatile来保障肯定的有序性。最驰名的例子就是单例模式外面的DCL(双重查看锁)。另外,能够通过synchronized和Lock来保障有序性,synchronized和Lock保障每个时刻是有一个线程执行同步代码,相当于是让线程程序执行同步代码,天然就保障了有序性。

volatile变量的个性

保障可见性,不保障原子性

  • 当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;
  • 这个写会操作会导致其余线程中的缓存有效。

禁止指令重排

重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种伎俩。重排序须要恪守肯定规定:
  • 重排序操作不会对存在数据依赖关系的操作进行重排序。

    比方:a=1;b=a; 这个指令序列,因为第二个操作依赖于第一个操作,所以在编译时和处理器运

    行时这两个操作不会被重排序。

  • 重排序是为了优化性能,然而不管怎么重排序,单线程下程序的执行后果不能被扭转

    比方:a=1;b=2;c=a+b这三个操作,第一步(a=1)和第二步(b=2)因为不存在数据依赖关系, 所以可能会发

    生重排序,然而c=a+b这个操作是不会被重排序的,因为须要保障最终的后果肯定是c=a+b=3。

    重排序在单线程下肯定能保障后果的正确性,然而在多线程环境下,可能产生重排序,影响后果,下例中的1和2因为不存在数据依赖关系,则有可能会被重排序,先执行status=true再执行a=2。而此时线程B会顺利达到4处,而线程A中a=2这个操作还未被执行,所以b=a+1的后果也有可能仍然等于2。

指令重排序

出于性能起因容许JVM和CPU从新排序程序中的指令,只有指令的语义含意放弃不变即可。例如,查看上面的指令:
COPYint a = 1;int b = 2;a++;b++;
这些指令能够按以下程序从新排序,而不会失落程序的语义含意:
COPYint a = 1;a++;int b = 2;b++;

然而,当其中一个变量是volatile变量时,指令重排序会呈现一个挑战。让咱们看看MyClass这个后面Java volatile教程中的例子中呈现的类:

COPYpublic class MyClass {    private int years;    private int months    private volatile int days;    public void update(int years, int months, int days){        this.years  = years;        this.months = months;        this.days   = days;    }}

一旦update()办法写入一个值days,新写入的值,以yearsmonths也被写入主存储器。然而,如果JVM从新排序指令,如下所示:

COPYpublic void update(int years, int months, int days){    this.days   = days;    this.months = months;    this.years  = years;}

days变量被批改时monthsyears的值依然写入主内存中,然而这一次它产生在新的值被写入monthsyears之前,也就是这两个变量的旧值会写入主存中,前面两句的写入操作只是写到缓存中。因而,新值不能正确地对其余线程可见。从新排序的指令的语义含意曾经扭转。

happens before

下面讲的是volatile变量本身的个性,对程序员来说,volatile对线程的内存可见性的影响比volatile本身的个性更为重要,也更须要咱们去关注。

从JSR-133开始,volatile变量的写-读能够实现线程之间的通信。

从内存语义的角度来说,volatile与锁有雷同的成果:volatile写和锁的开释有雷同的内存语义;volatile读与锁的获取有雷同的内存语义。

请看上面应用volatile变量的示例代码:
COPYclass VolatileExample {    int a = 0;    volatile boolean flag = false;    public void writer() {        a = 1;                   //1        flag = true;               //2    }    public void reader() {        if (flag) {                //3            int i =  a;           //4            ……        }    }}

假如线程A执行writer()办法之后,线程B执行reader()办法。依据happens before规定,这个过程建设的happens before 关系能够分为两类:

  1. 依据程序秩序规定,1 happens before 2; 3 happens before 4。
  2. 依据volatile规定,2 happens before 3。
  3. 依据happens before 的传递性规定,1 happens before 4。
上述happens before 关系的图形化表现形式如下:

上图中,每一个箭头链接的两个节点,代表了一个happens before 关系。彩色箭头示意程序程序规定;橙色箭头示意volatile规定;蓝色箭头示意组合这些规定后提供的happens before保障。

这里A线程写一个volatile变量后,B线程读同一个volatile变量。A线程在写volatile变量之前所有可见的共享变量,在B线程读同一个volatile变量后,将立刻变得对B线程可见。

Happens-Before 保障

为了解决指令重排序挑战,除了可见性保障之外,Java volatile关键字还提供“happens-before”保障。happens-before保障保障:
volatile 之前读写

如果读取/写入最后产生在写入volatile变量之前,读取/写入其余变量不能从新排序在写入volatile变量之后。
写入volatile变量之前的读/写操作被保障 “happen before” 写入volatile变量。请留神,产生在写入volatile变量之后的读/写操作仍然能够重排序到写入volatile变量前,只是不能相同。容许从后到前,但不容许从前到后。

volatile 之后读写

如果读/写操作最后产生在读取volatile变量之后,则读取/写入其余变量不能重排序到产生在读取volatile变量之前。请留神,产生在读取volatile变量之前的读/写操作仍然能够重排序到读取volatile变量后,只是不能相同。容许从前到后,但不容许从后到前。

上述 “happens-before”规定保障确保volatile关键字的可见性保障在强制执行。

COPYpublic class VolatileTest {    private volatile int vi = 1;    private int i = 2;    private int i2 = 3;    @Test    public void test() {        System.out.println(i);      //1  读取一般变量        i=3;                        //2  写入一般变量        //1 2 不能重排序到3之后,操作4能够重排序到3后面        vi = 2;                     //3  写入volatile变量        i2 = 5;                     //4  写入一般变量    }    @Test    public void test2() {        System.out.println(i);      //1  读取一般变量        //3不能重排序到在2前,但1能够重排序到2后        System.out.println(vi);     //2  读取volatile变量        System.out.println(i2);     //3  读取一般变量    }}

volatile注意事项

volatile 线程不平安

即便volatile关键字保障volatile变量的所有读取间接从主存储器读取,并且所有对volatile变量的写入都间接写入主存储器,依然存在申明volatile变量线程不平安。

在后面解释的状况中,只有线程1写入共享counter变量,申明counter变量为volatile足以确保线程2始终看到最新的写入值。

实际上,如果写入volatile变量的新值不依赖于其先前的值,则甚至能够多个线程写入共享变量,并且依然能够在主存储器中存储正确的值。换句话说,就是将值写入共享volatile变量的线程开始并不需要读取其旧值来计算其下一个值。

一旦线程须要首先读取volatile变量的旧值,并且基于该值为共享volatile变量生成新值,volatile变量就不再足以保障正确的可见性。读取volatile 变量和写入新值之间的短时间距离会产生竞争条件 ,其中多个线程可能读取volatile变量的同一个旧值,而后为其生成新值,并将该值写回主内存 - 笼罩彼此的值。

多个线程递增同一个计数器的状况正是 volatile变量并不平安的状况。以下局部更具体地解释了这种状况。

设想一下,如果线程1将值为0的共享变量counter读入其CPU高速缓存,将其减少到1并且不将更改的值写回主存储器。而后,线程2也从主存储器读取雷同的counter变量进入本人的CPU高速缓存,其中变量的值仍为0。而后,线程2也将计数器递增到1,也不将其写回主存储器。这种状况如下图所示:

线程1和线程2当初失去了同步。共享变量counter的理论值应为2,但每个线程的CPU缓存中的变量值为1,而在主内存中,该值仍为0。这是一个凌乱!即便线程最终将共享变量counter的值写回主存储器,该值也将是谬误的。

保障线程平安

正如我后面提到的,如果两个线程都在读取和写入共享变量,那么应用 volatile关键字是不平安的。 在这种状况下,您须要应用synchronized来保障变量的读取和写入是原子性的。读取或写入一个volatile变量不会阻塞其余线程读取或写入这个变量。为此,您必须在临界区四周应用synchronized关键字。

作为synchronized块的代替办法,您还能够应用java.util.concurrent包中泛滥的原子数据类型。例如,AtomicLong或者 AtomicReference或其余的。

如果只有一个线程读取和写入volatile变量的值,而其余线程只读取这个变量,那么此线程将保障其余线程能看到volatile变量的最新值。如果不将变量申明为volatile,则无奈保障。

volatile关键字也能够保障在64位变量上失常应用。

volatile的性能思考

读取和写入volatile变量会导致变量从主存中读取或写入主存,读取和写入主内存比拜访CPU缓存开销更大。拜访volatile变量也会阻止指令重排序,这是一种失常的性能晋升技术。因而,当您的确须要强制施行变量可见性时,才应用volatile变量。

原理

volatile能够保障线程可见性且提供了肯定的有序性,然而无奈保障原子性。在JVM底层volatile是采纳“内存屏障”来实现的。察看退出volatile关键字和没有退出volatile关键字时所生成的汇编代码发现,退出volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个性能:
  • 它确保指令重排序时不会把其前面的指令排到内存屏障之前的地位,也不会把后面的指令排到内存屏障的前面;即在执行到内存屏障这句指令时,在它后面的操作曾经全副实现;
  • 它会强制将对缓存的批改操作立刻写入主存;
  • 如果是写操作,它会导致其余CPU中对应的缓存行有效。

内存语义

volatile写的内存语义
当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。

以下面示例程序VolatileExample为例,假如线程A首先执行writer()办法,随后线程B执行reader()办法,初始时两个线程的本地内存中的flag和a都是初始状态。下图是线程A执行volatile写后,共享变量的状态示意图:

如上图所示,线程A在写flag变量后,本地内存A中被线程A更新过的两个共享变量的值被刷新到主内存中。此时,本地内存A和主内存中的共享变量的值是统一的。

volatile读的内存语义
当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为有效。线程接下来将从主内存中读取共享变量。

上面是线程 B 读同一个 volatile 变量后,共享变量的状态示意图:

如上图所示,在读flag变量后,本地内存B曾经被置为有效。此时,线程B必须从主内存中读取共享变量。线程B的读取操作将导致本地内存B与主内存中的共享变量的值也变成统一的了。

如果咱们把volatile写和volatile读这两个步骤综合起来看的话,在读线程B读一个volatile变量后,写线程A在写这个volatile变量之前所有可见的共享变量的值都将立刻变得对读线程B可见。

小结
上面对volatile写和volatile读的内存语义做个总结
  • 线程A写一个volatile变量,本质上是线程A向接下来将要读这个volatile变量的某个线程收回了(其对共享变量所在批改的)音讯。
  • 线程B读一个volatile变量,本质上是线程B接管了之前某个线程收回的(在写这个volatile变量之前对共享变量所做批改的)音讯。
  • 线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程本质上是线程A通过主内存向线程B发送音讯。

volatile内存语义的实现

前文咱们提到过重排序分为编译器重排序和处理器重排序。为了实现volatile内存语义,JMM会别离限度这两种类型的重排序类型。上面是JMM针对编译器制订的volatile重排序规定表:
是否能重排序第二个操作第二个操作第二个操作
第一个操作一般读/写volatile读volatile写
一般读/写NO
volatile读NONONO
volatile写NONO

举例来说,第三行最初一个单元格的意思是:在程序程序中,当第一个操作为一般变量的读或写时,如果第二个操作为volatile写,则编译器不能重排序这两个操作。

从上表咱们能够看出
  • 当第二个操作为volatile写操作时,不论第一个操作是什么(一般读写或者volatile读写),都不能进行重排序。这个规定确保volatile写之前的所有操作都不会被重排序到volatile写之后;
  • 当第一个操作为volatile读操作时,不论第二个操作是什么,都不能进行重排序。这个规定确保volatile读之后的所有操作都不会被重排序到volatile读之前;
  • 当第一个操作是volatile写操作时,第二个操作是volatile读操作,不能进行重排序。

  为了实现 volatile 的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。上面是基于激进策略的 JMM 内存屏障插入策略:

  • 在每个 volatile 写操作的后面插入一个 StoreStore 屏障(禁止后面的写与volatile写重排序)。
  • 在每个 volatile 写操作的前面插入一个 StoreLoad 屏障(禁止volatile写与前面可能有的读和写重排序)。
  • 在每个 volatile 读操作的前面插入一个 LoadLoad 屏障(禁止volatile读与前面的读操作重排序)。
  • 在每个 volatile 读操作的前面插入一个 LoadStore 屏障(禁止volatile读与前面的写操作重排序)。

  其中重点说下StoreLaod屏障,它是确保可见性的要害,因为它会将屏障之前的写缓冲区中的数据全副刷新到主内存中。上述内存屏障插入策略十分激进,但它能够保障在任意解决平台,任意的程序中都能失去正确的volatile语义。上面是激进策略(为什么说激进呢,因为有些在理论的场景是可省略的)下,volatile 写操作 插入内存屏障后生成的指令序列示意图:

其中StoreStore屏障能够保障在volatile写之前,其后面的所有一般写操作对任意处理器可见(把它刷新到主内存)。

另外volatile写前面有StoreLoad屏障,此屏障的作用是防止volatile写与前面可能有的读或写操作进行重排序。因为编译器经常无奈精确判断在一个volatile写的前面是否须要插入一个StoreLoad屏障(比方,一个volatile写之后办法立刻return)为了保障能正确实现volatile的内存语义,JMM采取了激进策略:在每个volatile写的前面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见模式是:一个写线程写volatile变量,多个度线程读同一个volatile变量。当读线程的数量大大超过写线程时,抉择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的晋升。从这里也可看出JMM在实现上的一个特点:首先确保正确性,而后再去谋求效率(其实咱们工作中编码也是一样)。

上面是在激进策略下,volatile读插入内存屏障后生产的指令序列示意图:

 上述volatile写和volatile读的内存屏障插入策略十分激进。在理论执行时,只有不扭转volatile写-读的内存语义,编译器能够依据具体情况疏忽不必要的屏障。在JMM根底中就有提到过各个处理器对各个屏障的反对度,其中x86处理器仅会对写-读操作做重排序。

上面咱们通过具体的示例代码来阐明
COPYclass VolatileBarrierExample {    int a;    volatile int v1 = 1;    volatile int v2 = 2;    void readAndWrite() {        int i = v1;           //第一个volatile读        int j = v2;           // 第二个volatile读        a = i + j;            //一般写        v1 = i + 1;          // 第一个volatile写        v2 = j * 2;          //第二个 volatile写    }    …                    //其余办法}
针对 readAndWrite() 办法,编译器在生成字节码时能够做如下的优化:

留神,最初的StoreLoad屏障不能省略。因为第二个volatile写之后,办法立刻return。此时编译器可能无奈精确判定前面是否会有volatile读或写,为了平安起见,编译器经常会在这里插入一个StoreLoad屏障。

下面的优化是针对任意处理器平台,因为不同的处理器有不同“松紧度”的处理器内存模型,内存屏障的插入还能够依据具体的处理器内存模型持续优化。以x86处理器为例,上图中除最初的StoreLoad屏障外,其它的屏障都会被省略。

后面激进策略下的volatile读和写,在 x86处理器平台能够优化成:

前文提到过,x86 处理器仅会对写 - 读操作做重排序。,x86处理器仅会对写-读操作做重排序。X86不会对读-读,读-写和写-写操作做重排序,因而在x86处理器中会省略掉这三种操作类型对应的内存屏障。在x86中,JMM仅需在volatile写前面插入一个StoreLoad屏障即可正确实现volatile写-读的内存语义。这意味着在x86处理器中,volatile写的开销比volatile读的开销会大很多(因为执行StoreLoad屏障开销会比拟大)。

为什么要加强volatile的内存语义

在 JSR-133 之前的旧 Java 内存模型中,尽管不容许 volatile 变量之间重排序,但旧的 Java 内存模型容许 volatile 变量与一般变量之间重排序。在旧的内存模型中,VolatileExample 示例程序可能被重排序成下列时序来执行:

在旧的内存模型中,当 1 和 2 之间没有数据依赖关系时,1 和 2 之间就可能被重排序(3 和 4 相似)。其后果就是:读线程 B 执行 4 时,不肯定能看到写线程 A 在执行 1 时对共享变量的批改。

因而在旧的内存模型中 ,volatile的写-读没有锁的开释-获所具备的内存语义。为了提供一种比锁更轻量级的线程之间通信的机制,JSR-133专家组决定加强volatile的内存语义:严格限度编译器和处理器对volatile变量与一般变量的重排序,确保volatile的写-读和锁的开释-获取一样,具备雷同的内存语义。从编译器重排序规定和处理器内存屏障插入策略来看,只有volatile变量与一般变量之间的重排序可能会毁坏volatile的内存语意,这种重排序就会被编译器重排序规定和处理器内存屏障插入策略禁止。

因为volatile仅仅保障对单个volatile变量的读/写具备原子性,而锁的互斥执行的个性能够确保对整个临界区代码的执行具备原子性。在性能上,锁比volatile更弱小;在可伸缩性和执行性能上,volatile更有劣势。如果读者想在程序中用volatile代替监视器锁,请肯定审慎,具体细节请参阅参考Java实践与实际:正确应用Volatile变量。

本文由传智教育博学谷狂野架构师教研团队公布。

如果本文对您有帮忙,欢送关注点赞;如果您有任何倡议也可留言评论私信,您的反对是我保持创作的能源。

转载请注明出处!