深入了解jvm-2Edition-线程安全与锁优化

1、什么是线程安全

  Brian Goetz 在 《Java Concurrency In Practice》中定义的线程安全为:

  “当多线程访问一个对象时,无论这些线程在运行时环境下以何种方式调度和交替执行, 

  在使用对象时,都不需要任何额外的同步,就可以得到正确的行为和结果,那么这个对象就是线程安全的。”

  Java中的线程安全可分为:

    1、不可变

      对象被安全构造(安全发布)后状态永不改变,那么永远是线程安全的。

    2、绝对线程安全

      满足Brian Goetz 的线程安全的定义。

    3、相对线程安全

      只保证对对象单独的操作是线程安全的,复合操作还是要进行同步。

    4、线程兼容

      对象本身不是线程安全的,但是可以通过调用端使用同步来保证安全使用。

    5、线程对立

      无论是否采取同步,都无法安全并发使用的代码。

      如:Thread.suspend() 和 Thread.resume() 有死锁风险。

2、线程安全实现方法

  1、互斥同步-阻塞同步

    通过使用互斥来保证同步。

    临界区、互斥量、信号量。

    synchronized同步代码块:

      可重入、阻塞或唤醒需要操作系统完成。非公平,只能绑定一个唤醒条件。

    ReentrantLock:

      可重入,等待可中断、可实现公平、可绑定多个唤醒条件。

    因为jdk对synchronized做了优化,能用synchronized实现需求时尽量使用synchronized。

  2、非阻塞同步

    阻塞同步是一种悲观的并发策略,认为只要没有正确的同步措施,就一定会出现问题。

    非阻塞同步则是乐观的,基于冲突检测,没有冲突就算成功,有冲突就解决冲突(重新试)。

    乐观并发策略需要硬件帮助,因为要保证操作和检测冲突是原子的。

    常用的原子指令有:

      Test-and-Set;

      Fetch-and-Increment;

      Swap;

      Compare-and-Swap;

      Load-Linked / Store-Conditional。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ConcurrencyWithAtomic {
    private static final int THREADS_COUNT=20;
    public static AtomicInteger race=new AtomicInteger(0);
    public static void increase(){
        race.incrementAndGet();
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService= Executors.newCachedThreadPool();
        for(int i=0; i<THREADS_COUNT; i++) {
            executorService.execute(()->{
                for(int j=0; j<THREADS_COUNT; j++)
                    increase();
            });
        }
        while(!executorService.awaitTermination(1000, TimeUnit.MILLISECONDS))
        executorService.shutdown();
        System.out.println(race);
    }
}

 

    其中incrementAndGet的源码为:

/**
     * Atomically increments the current value,
     * with memory effects as specified by {@link VarHandle#getAndAdd}.
     *
     * <p>Equivalent to {@code addAndGet(1)}.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }

/**
     * Atomically adds the given value to the current value of a field
     * or array element within the given object {@code o}
     * at the given {@code offset}.
     *
     * @param o object/array to update the field/element in
     * @param offset field/element offset
     * @param delta the value to add
     * @return the previous value
     * @since 1.8
     */
    @IntrinsicCandidate
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
    }

    可以看到,getAndAddInt(Object o, long offset, int delta) 方法是实现了栈封闭的。

  3、无同步方案

    可重入代码,栈封闭;

    ThreadLocal实现线程封闭。

3、锁优化

  1、旋转锁和自适应锁

    由于在挂起和恢复线程时,需要陷入内核,因此开销较大。

    因此,选择在锁持续时间短时,采用忙等待的形式。

    自适应锁就是虚拟机会自动设置忙等待的循环次数。

  2、锁消除

    由逃逸分析数据支持,在实现了栈封闭时,就不需要锁了。

    这时候就可以将锁消除掉。

  3、锁粗化

    我们写代码时,为了减少同步操作的数量,总是习惯将同步代码块缩小。

    但是,如果同步代码块小而密集的时候,频繁的进行互斥操作会导致性能损耗。

    这是,虚拟机会将同步代码块的范围扩大。

4、轻量级锁

  在没有多线程竞争的时候,减少互斥产生的性能消耗。

  HotSpot虚拟机对象头结构:

    对象头分为两部分,一部分用于存贮对象自身的运行时数据(HashCode、GC Age)。

    另一部分用于存储指向方法区中对应的元数据的引用。

    第一部分也被称为Mark Word,HotSpot虚拟机Mark Word采用了非固定的数据结构。

    在每种结构下,都有一个标志位。

    结构和标志位一起确定了对象的状态。

  

  

  在线程进入同步代码块时,如果此时对象没有被锁定,

  虚拟机将首先在线程的栈帧中建立一个锁记录空间(Lock Record),用于保存对象的当前Mark Word的拷贝。

  然后,虚拟机使用CAS操作将对象Mark Word的值更新为指向锁记录空间的指针。

  如果更新成功,那么该对象就被轻量级锁定了,Mark Word的标志位变为00。

  如果更新失败,检查对象的Mark Word是否指向当前线程,是,则可进入同步代码块。

  否,则说明对象已经被其他线程锁定了。

  此时,出现了两个以上的线程争用同一个锁,轻量级锁不再起效,要膨胀为重量级锁。

  锁标记的标记位要变为 10。

  释放锁的时候,也要通过CAS操作来完成。如果替换失败,要释放锁的同时,唤醒被挂起的线程。

  偏向锁:

    偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他线程获取,

    则持有锁的线程不需要再进行同步。

    一旦有线程尝试获取这个锁,偏向模式就结束。

 

 

  

 

  

 

      

 

posted @ 2021-09-17 12:45  Lqblalala  阅读(52)  评论(0)    收藏  举报