Java基础-Java中常用的锁机制与使用

Java基础-Java中常用的锁机制与使用

lock或互斥mutex是一种同步机制,主要用于在存在多线程的环境中强制对资源进行访问限制。锁的主要作用为强制实施互斥排他以及并发控制策略。锁一般需要硬件支持才可以有效的实施。这种支持通常采取一个或多个原子指令的形式,如test-and-setfetch-and-add或者compare-and-swap。这些指令允许单个进程测试锁是否空闲,如果空闲,则通过单个原子操作获取锁。

使用锁可以保证多线程的环境下同步执行,可以解决可见性/有序性/原子性问题。

一、锁的类型

Java主流锁
线程是否需要锁住同步资源?
锁住
悲观锁
不锁住
乐观锁
锁住同步资源失败,线程是否需要阻塞?
阻塞
不阻塞
自旋锁
适应性自旋锁
多个线程竞争同步资源的流程细节有没有区别?
不锁住资源,多个线程中只有一个能修改资源成功,其他线程就重试
无锁
同一个线程执行同步资源时自动获取资源
偏向锁
多个线程竞争同步资源时,没有获取资源的线程自旋等待锁释放
轻量级锁
多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒
重量级锁
多个线程竞争锁时是否需要排队?
排队
公平锁
先尝试插队,插队失败再排队
非公平锁
一个线程的多个线程能不能获取同一把锁?
可重入锁
不能
非可重入锁
多个线程能不能共享一把锁?
共享锁
不能
排他锁

二、CAS

CAS(Compare And Swap),比较并交换,为乐观锁

适用场景分析

CAS只能针对单个变量进行原子操作;不能操作代码块。(AtomicReference原子引用类可解决多属性变量的问题),适用于资源竞争较少(线程冲突较轻)的情况,自旋(循环)时间不会很长,不会浪费cpu太多资源。

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicityDemo {

    private static final AtomicInteger number = new AtomicInteger(1);

    public static void main(String[] args) throws InterruptedException {
        ArrayList<Thread> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    number.getAndIncrement();
                }
            });
            thread.start();
            list.add(thread);
        }
        for (Thread thread : list) {
            thread.join();
        }
        number.decrementAndGet();
        System.out.println("number:" + number);
    }
}

悲观锁与乐观锁

悲观锁

线程A尝试获取锁
线程B尝试获取锁
线程A执行中
等待
线程A执行完毕,释放锁
线程B尝试获取锁
线程B获取锁成功,并开始操作同步资源
线程A
同步资源
线程B
线程A
线程B
同步资源
线程A
线程B
同步资源

乐观锁

获取数据后直接操作
准备更新同步资源
先判断内存中同步资源是否被更新
同步资源没有被更新
同步资源被其他线程更新,更新或尝试
更新内存中同步资源的值
线程A
同步资源
线程A
同步资源
线程A
线程A
同步资源
  • 悲观锁适合写多读少的场景,因为先加锁能保证写操作的正确性。
  • 乐观锁适合读多写少的场景,因为读操作一般并不需要加锁(没有进行数据修改操作),因此乐观锁的无锁特性能使读性能有很大的提升(减少了加锁等待的时间)。

CAS原理

Y
N
开始
获取内存中的值A
此时内存中的值V==预期值A?
更新内存中的值B
结束
  • CAS有三个操作数,即内存值 v,旧操作数a,新操作数b。当需要更新vb时,需要先判断v值是否与之前的所见值a相同,若相同则将v赋值为b,若不同,则什么都不做。
  • CAS是一种非阻塞算法。利用JNI机制和CPUlock cmpxchg...指令来完成。

JNI机制:https://www.cnblogs.com/kexinxin/p/11689641.html

自旋锁/自适应自旋锁

自旋锁

当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断 锁是否能够被成功获取,直到获取到锁才会退出循环。

优点

阻塞或唤醒一个Java线程需要操作系统切换 CPU 状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。所以自旋锁就可以避免 CPU切换带来的性能、时间等影响。

缺点

自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。

使用方式

一个基于CAS实现的自旋锁:AtomicInteger

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;
}

do while循环操作中的判断条件compareAndSwapInt方法。整个“比较+更新”操作封装在compareAndSwapInt()中,整个过程就是自旋的去重试这个操作。

自定义自旋锁

定义锁

import java.util.concurrent.atomic.AtomicReference;

/**
 * @program: Demo
 * @description: 自定义自旋锁
 * @author: 岚樱时
 * @date: 2020-05-26 09:43
 */
public class CustomSpinLock {

    /**
     * 存放当前持有锁的线程
     * 当reference为null,锁没有被线程获取
     */
    private AtomicReference<Thread> reference = new AtomicReference<>();

    /**
     * 获取锁
     */
    public void lock(){
        Thread currentThread = Thread.currentThread();
        while (!reference.compareAndSet(null, currentThread)) {
            // 自旋判断锁是否被其他线程持有
            // reference为null时,当前锁没有被线程获取,compareAndSet返回true
            // TODO
        }
    }

    /**
     * 释放锁
     */
    public void unLock(){
        Thread currentThread = Thread.currentThread();
        if (reference.get() != currentThread) {
            // 锁被其他线程占用,当前线程无法释放锁
            return;
        }
        // 释放锁
        reference.set(null);
    }

}

测试自定义锁

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @program: Demo
 * @description: 自定义自旋锁测试类
 * @author: 岚樱时
 * @date: 2020-05-26 10:15
 */
public class TestCustomSpinLock {

    static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        // 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        // 创建计数器
        // 计数器的初始值是线程的数量,每当一个线程执行完成之后,计数器的值-1
        // 当计数器的值为0,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作
        CountDownLatch countDownLatch = new CountDownLatch(100);
        // 创建自定义自旋锁
        CustomSpinLock customSpinLock = new CustomSpinLock();
        for (int i = 0; i < 100; i++) {
            executorService.execute(() -> {
                customSpinLock.lock();
                ++count;
                customSpinLock.unLock();
                // 释放锁后,计数器值-1
                countDownLatch.countDown();
            });
        }
        // 调用await()方法,线程被挂起,等待计数器为0后继续执行
        countDownLatch.await();
        System.out.println(count);
        executorService.shutdown();
    }
}
  • lock()方法利用的CAS,当第一个线程A获取锁的时候,成功获取到锁,不会进入while循环。
  • 如果此时线程A没有释放锁,另一个线程B想获取锁,此时由于不满足CAS,所以就会进入while循环,不断判断是否满足CAS,直到线程A调用unlock方法释放锁,线程B才会获取锁。
启用自旋锁
参数 默认值或限制 说明
-XX:-UseSpinning java1.4.2和1.5需要手动启用,java6默认已启用 启用多线程自旋锁优化
-XX:PreBlockSpin=10 -XX:+UseSpinning必须先启用。对于jdk6来说已经默认启用了,默认自旋10次。 控制多线程自旋锁优化的自旋次数

自适应自旋锁

自适应意味着对于一个锁对象,线程的自旋时间是根据上一个持有该锁的线程的自旋时间以及状态来确定的。

例如:对于锁A对象来说,如果一个线程刚刚通过自旋获得了锁,并且该线程也在运行中,那么JVM会认为此次自旋操作也是有很大的机会可以拿到锁,因此它会让自旋的时间相对延长。但是如果对于锁B对象自旋操作很少成功的话,JVM甚至可能直接忽略自旋操作,将线程挂起或堵塞。

ABA问题

线程1准备用CAS修改变量A,在此之前,其他线程将变量的值由A替换为B,又由B替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但是实际上这时的现场已经和最初不同。

A
B
A

代码示例

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @program: Demo
 * @description: 测试ABA问题案例
 * @author: 岚樱时
 * @date: 2020-05-26 11:00
 */
public class AtomicDemo {

    public static AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println("当前线程:" + Thread.currentThread() + "\n初始值:" + atomicInteger);
            try {
                // 线程1等待,线程2对数据进行修改
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // CAS 期望1->2
            boolean CASStatus = atomicInteger.compareAndSet(1, 2);
            System.out.println("当前线程:" + Thread.currentThread() + "\nCAS操作结果:" + CASStatus);
        }, "线程1");

        Thread thread2 = new Thread(() -> {
            try {
                // 线程等待,确保线程1先执行
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // atomicInteger+1=>2
            atomicInteger.incrementAndGet();
            System.out.println("当前线程:" + Thread.currentThread() + "\nincrementAndGet结果:" + atomicInteger);
            // atomicInteger-1=>1
            atomicInteger.decrementAndGet();
            System.out.println("当前线程:" + Thread.currentThread() + "\ndecrementAndGet结果:" + atomicInteger);
        }, "线程2");

        thread1.start();
        thread2.start();
    }
}
解决方案

AtomicStampedReference主要维护一个对象引用以及一个可以自动更新的整数stamppair对象来解决ABA问题。

A:stamp1
B:stamp2
A:stamp3

AtomicStampedReference源码

package java.util.concurrent.atomic;

/**
 * An {@code AtomicStampedReference} maintains an object reference
 * along with an integer "stamp", that can be updated atomically.
 *
 * <p>Implementation note: This implementation maintains stamped
 * references by creating internal objects representing "boxed"
 * [reference, integer] pairs.
 *
 * @since 1.5
 * @author Doug Lea
 * @param <V> The type of object referred to by this reference
 */
public class AtomicStampedReference<V> {

    private static class Pair<T> {
      	// 维护对象引用
        final T reference;
      	// 用于标志版本
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

    private volatile Pair<V> pair;
  
  	/****/
 
  	/**
  	 	* expectedReference	更新之前的原始值
  		* newReference			将要更新的新值
  		* expectedStamp			期待更新的标志版本
  		* newStamp					将要更新的标志版本
  		*/
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
  	
  	/****/
}

测试代码

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @program: Demo
 * @description: 解决ABA案例
 * @author: 岚樱时
 * @date: 2020-05-26 12:50
 */
public class AtomicDemo1 {

    private static final AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 0);

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println("当前线程:" + Thread.currentThread() + "\n初始值a:" + atomicStampedReference.getReference() + "\n");
            // 获取当前标识版本
            int stamp = atomicStampedReference.getStamp();
            try {
                // 线程等待3s,保证干扰线程执行
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 判断reference&&stamp
            boolean CASStatus = atomicStampedReference.compareAndSet(1, 2, stamp, stamp + 1);
            System.out.println("当前线程:" + Thread.currentThread() + "\nCAS判断结果:" + CASStatus + "\n");
        }, "线程1");

        Thread thread2 = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println("当前线程:" + Thread.currentThread() + "\nincrement值:" + atomicStampedReference.getReference()+"\n");
            atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println("当前线程:" + Thread.currentThread() + "\ndecrement值:" + atomicStampedReference.getReference() + "\n");
        },"线程2");

        thread1.start();
        thread2.start();
    }
}

三、synchronized

使用synchronized关键字,可以解决可见性/有序性/原子性问题。

使用方式

  • 同步实例方法,锁是当前实例对象
  • 同步类方法,锁是当前类对象
  • 同步代码块,锁是括号里面的对象
/**
 * @program: Demo
 * @description: Synchronized关键字使用方式案例
 * @author: 岚樱时
 * @date: 2020-05-26 13:38
 */
public class SynchronizedDemo {

    static int counter = 0;

    static final Object lock = new Object();

    public static void main(String[] args) {
        // 锁是括号里面的对象
        synchronized (lock) {
            counter++;
        }
    }

    /**
     * 锁是当前类对象
     */
    public synchronized static void m1(){
        counter++;
    }
}

特性

原子性

原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

可见性

synchronized对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新都主存当中,保证资源变量的可见性,如果某个线程占用了该锁,其他线程就必须在锁池中等待锁的释放。

有序性

synchronized保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。

可重入性

当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁。就是一个线程拥有了锁仍然还可以重复申请该锁。

偏向锁

原理

在没有多线程竞争的情况下,轻量级锁的获取以及释放多次CAS原子指令,而偏向锁只依赖一次CAS原子指令置换ThreadID,不过一旦出现多个线程竞争时必须撤销偏向锁,所以撤销偏向锁消耗的性能必须小于之前节省下来的CAS原子操作的性能消耗。

  • JDK1.6之后默认开启偏向锁
  • 偏向锁延迟生效,在应用程序启动几秒钟之后才激活,可以使用
  • XX:BiasedLockingStartupDelay=0 参数关闭延迟
  • 如果确定应用程序中所有锁通常情况下处于竞争状态,可以通过-XX:-UseBiasedLocking参数关闭偏向锁。

偏向锁获取

  • 通过markup mark = obj->mark()获取对象的markOop数据mark,即对象头的Mark Word

  • 判断mark是否为可偏向状态,即mark的偏向锁标志位为1,锁标志位为01

  • 判断markJavaThread的吗状态

    • 如果为空,则进入步骤4
    • 如果指向当前线程,则执行同步代码块
    • 如果指向其他线程,进入步骤5
  • 通过CAS原子指令设置mark中的JavaThread为当前线程ID,执行CAS成功,执行同步代码块

  • 如果执行CAS失败,表示当前存在多个线程竞争锁,火的偏向锁的线程达到全局安全点(safepoint)时被挂起,撤销偏向锁,并升级为轻量级。升级完成后被阻塞在安全点的线程继续执行同步代码块

偏向锁撤销

只有当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,偏向锁的撤销由BiasedLocking::revoke_at_safepoint方法实现。

  • 偏向锁的撤销动作必须等待全局安全点
  • 暂停拥有偏向锁的线程,判断锁对象是否处于被锁定的状态
  • 撤销偏向锁,恢复到无锁(标志位为01)(直接调用对象的hashcode方法)或轻量级锁(标志位为00)(多线程交替获取对象锁)的状态

实践

偏向锁的开启关闭

依赖包

<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

代码案例实现

import org.openjdk.jol.info.ClassLayout;

/**
 * @program: Demo
 * @description: 偏向锁Demo
 * @author: 岚樱时
 * @date: 2020-05-26 14:35
 */
public class BiasedLockDemo {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("开启偏向锁");
        biasedLockStart();
        System.out.println("关闭偏向锁");
        biasedLockClose();
    }

    /**
     * 开启偏向锁
     * @throws InterruptedException
     */
    public static void biasedLockStart() throws InterruptedException{
        // 加锁前,偏向锁生效有延迟,不会立即生效,输出001
        String result = ClassLayout.parseInstance(new BiasedLockDemo.Model()).toPrintable();
        System.out.println("result:" + result);
        Thread.sleep(5000);

        // 加锁前,延迟一定时间后,偏向锁生效,输出101
        Model model = new BiasedLockDemo.Model();
        result = ClassLayout.parseInstance(model).toPrintable();
        System.out.println("result:" + result);

        synchronized (model) {
            result = ClassLayout.parseInstance(model).toPrintable();
            System.out.println("result:" + result);
        }

        // 解锁后,对象头不变
        result = ClassLayout.parseInstance(model).toPrintable();
        System.out.println("result:" + result);
    }

    /**
     * 关闭偏向锁
     * @throws InterruptedException
     */
    public static void biasedLockClose() throws InterruptedException {
        // 加锁前,输出001
        String result = ClassLayout.parseInstance(new Model()).toPrintable();
        System.out.println(result);
        Thread.sleep(5000);
        System.out.println("================================================");

        // 加锁前,延迟i一定时间后,仍输出001
        Model model = new Model();
        result = ClassLayout.parseInstance(model).toPrintable();
        System.out.println(result);
        System.out.println("================================================");

        // 加锁时,输出轻量级锁信息 线程栈中的lock record锁记录指针和00状态位
        synchronized (model) {
            result = ClassLayout.parseInstance(model).toPrintable();
            System.out.println(result);
            System.out.println("================================================");
        }

        // 解锁后,输出001,无锁状态
        result = ClassLayout.parseInstance(model).toPrintable();
        System.out.println(result);
    }

    public static class Model{

    }
}
开启偏向锁
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 90 00 0b (00000101 10010000 00000000 00001011) (184586245)
      4     4        (object header)                           b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 90 00 0b (00000101 10010000 00000000 00001011) (184586245)
      4     4        (object header)                           b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

关闭偏向锁
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

================================================
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

================================================
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 90 00 0b (00000101 10010000 00000000 00001011) (184586245)
      4     4        (object header)                           b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

================================================
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 90 00 0b (00000101 10010000 00000000 00001011) (184586245)
      4     4        (object header)                           b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

轻量级锁

在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗,直接使用轻量级锁。但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁。

轻量级锁获取

当关闭偏向锁功能,或多个线程竞争偏向锁导致锁升级为轻量级锁,会尝试获取轻量级锁,其入口位于ObjectSynchronizer::slow_enter

轻量级锁释放

轻量级锁的释放通过ObjectSynchronizer::fast_exit完成

  • 取出在获取轻量级锁时保存在BasicLock对象的mark数据dhw
  • 通过CAS尝试把dhw替换到当前的Mark Word,如果CAS成功,说明成功的释放了锁,否则执行步骤3
  • 如果CAS失败,说明有其他线程在尝试获取锁,这是该锁已经升级为重量级锁。执行重量级锁释放流程。

重量级锁

重量级锁通过对象内部的监视器monitor实现,其中monitor的本质是依赖于底层操作系统的MutexLock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。

锁膨胀过程通过ObhectSynchronizer::inflate函数实现。

monitor对象

  • 当一个线程需要获取Object的锁时,会被放入EntrySet中进行等待。
  • 如果该线程获取到了锁,成为当前的owner。
  • 如果根据程序逻辑,一个获得了锁的线程缺少某些外部条件,而无法继续进行下去(例如生产者发现队列已满或者消费者发现队列为空),那么该线程可以通过调用wait方法将锁释放,进入wait set中阻塞进行等待。
  • 其他线程在这个时候有机会获得锁,去进行其他的任务,从而使之前不成立的外部条件成立,这样先前被阻塞的线程就可以重新进入EntrySet去竞争锁。

锁膨胀

各种锁比较

优点 缺点 使用场景
偏向锁 加锁不需要额外消耗,执行非同步方法仅有纳秒差距。 如果线程见存在竞争,会带来额外的撤销锁的消耗。 只有一个线程访问同步块场景。
轻量级锁 竞争的线程不会堵塞,提高了程序的响应速度。 如果始终得不到锁,竞争的线程使用自旋,会消耗CPU 追求响应时间,同步块指向性速度非常快。
重量级锁 线程竞争不会自旋,不消耗CPU。 线程堵塞,响应时间慢。 追求吞吐量,同步块执行时间较长。

锁消除

锁消除是虚拟机对锁的优化处理,JIT即使编译器(把热点代码编译成与本地平台相关的机器码,并且进行各种层次的优化)对运行上下文进行扫描,去除不可能存在竞争的锁,比如下面两个方法的执行效率是一样的,由于object锁是私有变量,不存在竞争关系。

public void lockElimination(){
    Object o = new Object();
    synchronized (o) {
        System.out.println("Say Hello!");
    }
}

public void lockEliminationExt(){
    Object o = new Object();
    System.out.println("Sqy Hello!");
}

锁粗化

锁粗化湿 虚拟机对锁的另一种优化处理。通过扩大锁的范围,避免反复加锁和释放锁。比如下面两个方法经过锁粗化优化之后执行效率一样了。

public void lockCoarse(){
    for (int i = 0; i < 10000; i++) {
        synchronized (SynchronizedDemo.class) {
            System.out.println("Say Hello!");
        }
    }
}

public void lockCoarseExt(){
    synchronized (SynchronizedDemo.class) {
        for (int i = 0; i < 10000; i++) {
            System.out.println("Say Hello!");
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_43512625/article/details/106374169