并发编程实战(2). 线程安全性

线程安全性

什么是线程安全性?

在《Java Concurrency In Practice》中,有这么一段对于线程安全性的描述,
被广泛引用:

当多个线程访问某个类时,不管运行时环境采取何种调度方式或者这些线程将如何交替执行,并且在
主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的
---- P13. Java Concurrency In Practice

而线程安全性主要体现在3个方面:

  • 原子性
  • 可见性
  • 有序性

下面我们使用代码例子来从这3个方面来测试线程安全性

原子性

说起原子性,我们不得不介绍java.util.concurrency.atomic包我们现在对上一节
ConcurrencyTest测试类代码进行改进,具体改进如下:

public static AtomicInteger count = new AtomicInteger(0);

// ...

log.info("Count: {}", count.get());

// ... 

private static void add(){
    count.incrementAndGet();
    // or count.getAndIncrement();
}

现在我们测试运行,发现每次测试,输出都为Count: 5000
现在我们可以给此类注释为@ThreadSafe

现在我们关键分析一下count.incrementAndGet();操作。

count.incrementAndGet();public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}// sun.misc.Unsafe.class
public native int getIntVolatile(Object var1, long var2);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

getIntVolatilecompareAndSwapInt都是native底层实现
现在看到getAndAddInt函数:

  • var1就是count对象本身
  • var2“当前值”(工作内存,可能并没有刷写到内存值上,导致不同步)
  • var4需要增加的值,这里为1
  • var5从底层获取的"当前值"
  • compareAndSwapInt:如果当前值var2与底层获取的当前值var5相等,那么最终就将结果加上一个偏移量var5

注意,上面所说的"当前值"有着特殊的语义,这个需要我们了解Java的内存模型中关于工作内存主内存的概念。

compareAndSwapInt也就是我们常说的CAS(Compare And Swap)
具体参考Java Concurrency In Practice P262深入理解Java虚拟机 P394

上面的compareAndSwapInt也可以用深入理解Java虚拟机里面的描述来理解:

  • CAS指令需要3个操作数:V,A,B。其中V为底层内存值,A为旧的预期值,B为需要替换的新值。CAS指令执行时,
    当且仅当V符合旧的预期值A时,处理器才能用新值B更新V的值。
  • CAS为原子操作

AtomicLong 与 LongAddr区别,参考文章:
https://www.jianshu.com/p/ec045c38ef0c

AtomicIntegerFieldUpdater。原子更新volatile non-static字段。

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

@Slf4j
public class AtomicUpdateFiled {

    private static AtomicIntegerFieldUpdater<AtomicUpdateFiled> updater =
                        AtomicIntegerFieldUpdater.newUpdater(AtomicUpdateFiled.class, "count");

    @Getter
    public volatile int count = 100; // 不能用static修饰

    private static AtomicUpdateFiled cls = new AtomicUpdateFiled();

    public static void main(String[] args) {
        if( updater.compareAndSet(cls, 100, 200) ){
            log.info("update success 1, {}", cls.getCount());
        }

        if( updater.compareAndSet(cls, 100, 200) ){
            log.info("update success 2, {}", cls.getCount());
        } else {
            log.info("update fail, {}", cls.getCount());
        }
    }
}
/*
运行结果如下:
update success 1, 200
update fail, 200
*/

CAS的ABA问题解决方案:AtomicStampedReference类。

每次对象状态被改变,它的版本号+1。

可见性

共享变量在线程间不可见的原因:

  • 线程交叉执行
  • 重排序结合线程交叉执行
  • 共享变量更新后的值没有在工作内存与主存间及时更新

你可能感兴趣的:(并发编程,并发编程)