即 JMM(Java Memory Model),它定义了主存、工作内存抽象概念。体现在
示例:主线程通过修改公共变量,使t1线程退出,但下面的 t1 线程不会退出:
public class VisiblityTest {
public static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(flag) {
//
}
});
t1.start();
Thread.sleep(1000);
flag = false;
System.out.println("修改flag……");
}
}
原因:
内存划分为主存和工作内存,主存是共享信息存储的位置,工作内存是线程私有信息存放的位置。因为 t 线程要频繁从主内存中读取 run 的值,所以 run 会被放入高速缓存区,即使主线程修改了主内存中的 run 值,也对 t 线程不可见。
解决方式:
(1)将变量声明为 volatile :
volatile static boolean flag = true;
(2)加锁:加锁也能解决可见性问题。
注意:volatile 只能保证可见性(每次读到的都是最新的值),不能保证原子性,适用于一个线程写一个线程读的情况(如两阶段终止模式)。Synchronized 既可以保证可见性又能保证原子性。但是 Synchronized 属于重量级锁,volatile 是轻量级的。
犹豫模式,用于一个线程发现另一个线程已经做了某件事,就直接退出避免重复执行。经常用于避免线程重复启动。
示例避免线程重复启动:
class MonitorService {
private volatile boolean isStart = false;
private volatile boolean isStop = false;
public void start() {
synchronized (this) {
if (isStart) {
System.out.println("线程已启动……");
return;
}
isStart = true;
}
new Thread(() -> {
//前面已经保证了只有一个线程能执行下面的代码
while (!isStop) {
System.out.println("执行监控……");
}
//这里没有用锁,需要通过 volatile 保证可见性
isStart = false;
System.out.println("监控线程已退出……");
}).start();
}
public synchronized void stop() {
isStart = false;
isStop = true;
}
}
指令重排:JVM 在不影响正确性的前提下,可以调整语句的执行顺序。多线程下指令重排会影响正确性。
示例:下列两个方法分别使用两个线程调用,result 的值除了 1 和 10,还可能出现 0 的情况。这就是指令重排在多线程下所产生的问题:
int num = 0;
boolean flag = false;
public void method1(int result) {
if(flag) {
result = num;
} else {
result = 1;
}
}
public void method2() {
num = 10;
flag = true; // flag 为 true 时,num 不一定为 10
}
解决方式:volatile。加在某个变量前可以保证该变量之前的代码都禁止重排序。
// 由于 flag 赋值在 num 之后,所以这里只需要给 flag 变量加 volatile
volatile boolean flag = false;
以下单例模式通过 double-checked locking 保证了只有首次获取实例对象时才加锁:
final class Singleton {
private Singleton() { }
private static Singleton instance = null;
public static Singleton getInstance() {
// double-checked locking 保证只有第一次创建对象时才进入同步代码块,提高效率
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
return new Singleton();
}
}
}
return instance;
}
}
但是,有序性问题会使上述代码出错,虽然 synchronized 也能保证有序性,但是在锁之外的 instance 对象没有受到保护。解决方式也是加 volatile。
private static volatile Singleton instance = null;
CAS 即 compare and set,它配合 Volatile 可以实现无锁并发。原理是通过不断地尝试获取共享变量的最新值直至成功,来解决并发带来的安全性问题。 CAS 其实采用了乐观锁的思想。
CAS 必须 配合 Volatile 才能发挥效果(因为需要时刻获取最新值),下面实例中的 AtomicInteger 的实现就采用了 Volatile。
public class CASTest {
public static void main(String[] args) {
List<Thread> list = new ArrayList<>();
AccountCAS accountCAS = new AccountCAS(1000);
for (int i = 0; i < 1000; i++) {
Thread t = new Thread(()->{
accountCAS.withdraw(1);
});
list.add(t);
}
list.forEach(Thread::start);
list.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("余额: " + accountCAS.getBalance());
}
}
class AccountCAS {
//余额
private AtomicInteger balance;
public AccountCAS(int balance) {
this.balance = new AtomicInteger(balance);
}
public Integer getBalance() {
return balance.get();
}
//取款操作
public void withdraw(Integer amount) {
while(true) {
int prev = balance.get();
int next = prev - amount;
//比较并设值,只有返回值为 true 才算成功,并退出循环
if(balance.compareAndSet(prev, next)) {
break;
}
}
}
}
原理图:
CAS的性能分析:无锁情况下,即使重试失败,也不会阻塞发生上下文切换,能够提高运行效率。但是由于无锁时线程一直在运行,如果没有 CPU 资源,分不到时间片的情况下,也会发生上下文切换。CAS 适合于线程数小于 CPU 核数的情况。但时如果竞争激烈,重试必然发生时,采用 CAS 效率反而会受到影响。
由 Java.util.current 包提供的一些原子类。这些类中的方法的操作都是原子性的。
(1)原子整数
AtomicInteger、AtomicBoolean、AtomicLong
上方示例代码中 compareAndSet 必须配合 while(true) 使用,但是 AtomicInteger 提供了更为简单的写法:
//取款操作
public void withdraw(Integer amount) {
balance.getAndAdd(-1 * amount);
}
更加通用的方法:
//取款操作
public void withdraw(Integer amount) {
balance.updateAndGet(x -> x-1); //传入一个IntUnaryOperator参数
}
(2)原子引用
AtomicReference(判断原子引用的值是否发生了变化)、AtomicStampedReference(带版本号的原子引用、追踪原子引用的整个变化过程)、AtomicMarkableReference(stamp的简化版,只判断原子引用是否被其他线程更改过)
示例:
class DecimalAccountCAS {
private AtomicReference<BigDecimal> balance;
public DecimalAccountCAS(BigDecimal balance) {
this.balance = new AtomicReference<>(balance);
}
public BigDecimal getBalance() {
return balance.get();
}
public void withdraw(BigDecimal amount) {
balance.updateAndGet( x -> x.subtract(amount));
}
}
(3)原子数组
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
(4)字段更新器
原子更新类中的字段:AtomicReferenceFieldUpdater、AtomicLongFieldUpdater、AtomicIntegerFieldUpdater
字段需要声明为 volatile:
private volatile String name;
使用:
AtomicReferenceFieldUpdater updater =
AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
updater.compareAndSet(.....)
(5)累加器
LongAdder、DoubleAdder的 increment() 方法性能优于前面的 getAndIncrement() 方法。它的原理是在有竞争时,设置多个累加单元,Thread-0 累加 Cell[0] 的结果,Thread-1 累加 Cell[1] 的结果……(上限为CPU核数)。