线程安全性
标签:Java并发

线程安全性

1. 三个特性

  1. 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作
  2. 可见性:一个线程对主内存的修改可以被其他线程观察到
  3. 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该结果一般杂乱无序。

2. 原子性

原子操作就是对访问同一个状态的所有操作(包括操作本身)来说,这个操作是以一个原子方式执行的操作。

2.1 原子性-Atomic包

AtomicXXX: CAS,Unsafe.compareAndSwapInt

AtomicLong: 低并发,准确数值要用

LongAddr: 将AtomicLong拆分,在统计时如果有并发更新,可能会导致误差,高并发时,优先使用。

compareAndSet

AtomicReference AtomicReferenceFieldUpdater

AtomicStampReference:CAS的ABA问题

2.2 原子性-Synchronized

每个Java对象都可以用作一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock)。线程在进入同步代码块的之前会自动获得锁,并且在退出同步代码块时会自动释放锁,无论是正常退出还是发生异常退出都会自动释放锁。获得锁的唯一途径就是进入由这个锁保护的同步代码块或方法。

内置锁是可重入的,因此一个线程试图请求一个已经由它获得的锁的时候,那么这个请求是会成功的。重入是实现方法是:为每个锁关联一个获取计数值和一个所有者线程。子类改写父类的Synchronized方法,由于锁是可以重入的,避免了死锁的发生。

2.3 原子性对比

3. 可见性

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

3.1 JMM关于Synchronized的两条规定

3.2 可见性-volatile

通过加入内存屏障禁止重排序优化来实现

使用volatile依赖于两个条件:

  1. 对变量的写操作不依赖当前值,或者能够确保只有单一的线程修改变量的值
  2. 变量不需要与其他状态的状态变量共同参与不变约束

volatile适合做状态标记量

volatile boolean inited=false;

//线程1
context=loadContext();
inited=true;

// 线程2
while (!inited) {
    sleep();
}
doSomethingWithConfig(context);

4. 有序性

Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,会影响到多线程并发执行的正确性。

4.1 happens-before原则

  1. 程序次序规则:一个线程内,按照代码顺序,准确的说是控制流顺序,。(一段程序代码在执行在单个线程中看起来是有序的,虚拟机会对不存在数据依赖性的代码进行指令重排序,无法保证在多线程中程序执行的正确性)
  2. 锁定规则:一个unlock操作先行发生于后面对lock操作,后面指时间上的先后顺序。
  3. volatile变量规则:对一个volatile变量的。后面也指时间上的后面。
  4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C。
  5. 线程启动规则:Thread对象的start()方法
  6. 线程中断规则:对线程的interrupt()方法的调用。可以通过Thread.interrupted()方法检测到是否有中断发生。
  7. 线程终结规则:线程中所有的操作都,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值手段检测到线程以及终止执行。
  8. 对象终结规则:一个对象的初始化完成

如果两个线程的执行次序无法从8个happens-before操作中推导出来,那么虚拟机将无法保证两个线程有序性,虚拟机可以随意对他们进行重排序

  • 6 min read

CONTRIBUTORS


  • 6 min read