.课程网站
线程安全性
原子性-Atomic包
1、CAS(Compare and Swap)
CAS:Compare and Swap的意思,比较并操作。很多的cpu直接支持CAS指令。CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 CAS算法:unsafe.compareAndSwapInt(this, valueOffset, expect, update); 如果valueOffset位置(CPU内存)包含的值与expect值相同,则更新valueOffset位置的值为update,并返回true,否则不更新,返回false。
看一下AtomicInteger.getAndIncrement()的源码:调用了Unsafe类的getAndAddInt(obj, valueOffset, 1)方法。
getAndIncrement源码var1为当前调用对象,即AtomicInteger实例;
var2为value属性在内存中的位置(volatile修饰的,保证多个线程访问都是与主内存一致的值);
var4为需要加的值,这里为1;
var5在1处:为CPU内存中当前value的值;在2处:为期望值;
在多线程情况下,1->2之间,有可能其他线程修改了CPU内存中的value的值,导致value与预期的var5不一致(compareAndSwapInt()会再次去内存取最新value的值),于是无法执行update操作(var5+var4),需要重新获取当前CPU内存中的value值,继续走compareAndSwapInt()方法进行验证,直到value与预期的var5一致,才执行update操作。
2、AtomicLong、LongAdder
当线程竞争很激烈时,AtomicXXX的底层while判断条件中的CAS会连续多次返回false,这样就会造成无用的循环,循环中读取volatile变量的开销本来就是比较高的。因此,在高并发时,AtomicXXX并不是那么理想的计数方式,此时应该使用LongAdder。
LongAdder是根据ConcurrentHashMap的设计原理-锁分段来实现的。它里面维护一组按需分配的技术单元,并发计数时, 不同的线程可以在不同的计数单元上进行计数,这样减少了多线程竞争,提高了并发效率。取值时,把多个计数单元的值求和就行了。
LongAdder在统计的时候如果有并发更新,可能会导致结果有些误差。对于需要准确的数值的情况,比如序号生成,不应该使用LongAdder,还是应该采用全局唯一的AtomicLong。
3.AtomicReference、AtomicReferenceFieldUpdater
AtomicReference:用法同AtomicInteger一样,但是可以放各种对象。
AtomicReference代码演示AtomicReferenceFieldUpdater:原子性的去更新某一个类的实例指定的某一个字段。
AtomicReferenceFieldUpdater代码演示4、AtomicStampReference:CAS的ABA问题
ABA问题:在CAS操作的时候,其他线程将变量的值A改成了B,接着由B改成了A,本线程使用期望值A与当前变量进行比较的时候,发现A变量没有变,于是CAS就将A值进行update操作,这个时候实际上A值已经被其他线程改变过,这与设计思想是不符合的。
解决思路:每次变量更新的时候,把变量的版本号加一,这样只要变量被某一个线程修改过,该变量版本号就会发生递增操作,从而解决了ABA问题。 AtomicStampReference类底层就是这样设计
5、AtomicBoolean(平时用的比较多)
AtomicBoolean代码演示-1 AtomicBoolean代码演示-2使用场景:用于在多线程情况下,保证某一段代码只被执行一次。
原子性-锁
Synchronized:依赖JVM (主要依赖JVM实现锁,因此在这个关键字作用对象的作用范围内,都是同一时刻只能有一个线程进行操作的)。
Lock:依赖特殊的CPU指令。
1、原子性-Synchronized
1、修饰代码块:大括号括起来的代码,作用于调用的对象
2、修饰方法:整个方法,作用于调用对象
3、修饰静态方法:整个静态方法,作用于所有对象
4、修饰类:括号括起来的部分,作用于所有对象
1和2的作用效果是一致的;3和4的作用效果是一致的。
2、原子性-对比
synchronized:不可中断锁,适合竞争不激烈,可读性好。
lock:可中断锁(调用unlock()),多样式同步,竞争激烈时能维持常态。
Atomic:竞争激烈时能维持常态,比lock性能好;但是只能同步一个值。
可见性
导致共享变量在线程中不可见的原因:
1、线程交叉执行
2、重排序结合线程交叉执行
3、共享变量更新后的值没有在工作内存与主内存间及时更新
可见性-synchronized
JMM关于Synchronized的两条规定:
1、线程解锁前,必须把共享变量的最新值刷新到主内存。
2、线程加锁时,将清空工作内存中的共享变量的值, 从而使用共享变量时需要从主内存中重新获取最新的值(注意,加锁与解锁是同一把锁)。
可见性-volatile
通过加入内存屏障和禁止重排序优化来实现。
volatile写指令 volatile读指令 自增执行步骤1、对volatile变量写操作时,会在写操作后加入一条store屏障指令,将工作内存中的共享变量值刷新到主内存。
2、对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。
小结:由于volatile没有原子性,所以volatile不适用于基于当前值的操作,适用于标识操作。
有序性
Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
Happens-before原则,先天有序性,即不需要任何额外的代码控制即可保证有序性,java内存模型一共列出了八种Happens-before规则,如果两个操作的次序不能从这八种规则中推倒出来,则不能保证有序性(本文就不列出8种规则了)。
网友评论