Java线程4-锁

Java线程4-锁

  • Java中锁的种类
    • 公平锁/非公平锁
    • 可重入锁
    • 独享锁/共享锁
    • 互斥锁/读写锁
    • 乐观锁/悲观锁
    • 分段锁
    • 偏向锁/轻量级锁/重量级锁
  • Java中锁的验证与使用
    • 1. synchronized
    • 2. ReentrantLock
      • 构造ReentrantLock为非公平锁
      • 构造ReentrantLock为公平锁
    • 3. ReentrantReadWriteLock
  • 参考文档
  • 相关面试题
    • Java有什么锁类型?
    • 乐观锁和悲观锁了解吗?JDK中涉及到乐观锁和悲观锁的内容?

Java中锁的种类

公平锁/非公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁;非公平锁是指多个线程不是按照申请锁的顺序来获取锁,有可能后申请锁的线程先获得锁。
非公平锁的优点在于其吞吐量比公平锁大;非公平锁可能会造成优先级反转或者饥饿现象。
对于 synchronized 而言,其是非公平锁。由于其不像 ReentrantLock 是通过 AQS 来实现的线程调度,所以并没有任何办法使其变成公平锁。
对于 ReentrantLock而言,可通过构造函数指定该锁是否是公平锁,默认是非公平锁。

可重入锁

可重入锁又名递归锁,是指对于同一个线程,在外层方法获取锁的时候,进入内层方法时会自动对内层方法也加上同一把锁(对外层方法进行加锁时,内层方法会自动加锁)。可重入锁的一个好处是可一定程度避免死锁。
如下代码便是可重入锁的一个示例,如果不是可重入锁的话,setB()方法可能不会被当前线程执行,可能造成死锁。

synchronized void setA() throws Exception{
    Thread.sleep(1000);
    setB();
}

synchronized void setB() throws Exception{
    Thread.sleep(1000);
}

对于 Java ReentrantLock 而言,其名字是 Re entrant Lock,即是可重入锁。
对于 synchronized 而言,也是一个可重入锁。

独享锁/共享锁

独享锁是指该锁只能同时被一个线程所持有;共享锁是指该锁可同时被多个线程所持有;
独享锁与共享锁都是通过AQS来实现的,通过实现不同的方法,来实现独享或共享;
对于 synchronized 而言,其是独享锁;
对于 Lock 的实现类 ReentrantLock 而言,其是独享锁;
对于 Lock 的实现类 ReadWriteLock 而言,其读锁是共享锁,其写锁是独享锁;
读锁的共享锁可保证并发读是非常高效的,读写、写读、写写是互斥的;

互斥锁/读写锁

上面说到的独享锁/共享锁是一种广义的说法,而互斥锁/读写锁则是其具体实现;互斥锁在Java中的具体实现就是ReentrantLock,读写锁在Java中的具体实现则是ReadWriteLock。

乐观锁/悲观锁

分段锁

偏向锁/轻量级锁/重量级锁

Java中锁的验证与使用

1. synchronized

synchronizd 是可重入锁、非公平锁、独享锁
本示例可验证synchronizd 是可重入锁、非公平锁、独享锁
代码: SynchronizedTest.java
代码运行效果如下图,从图中可看出,在 synchronizd 修饰的 get/set 方法中,线程从get()方法中顺利进入了set()方法,说明 synchronizd是可重入锁;查看线程 run 的顺序,可得出线程申请锁的顺序是 0 -> 1 -> 2 -> 3 -> 4;但是查看线程enter/leave的顺序,可得出线程获取锁的顺序 0 -> 2 -> 1 -> 3 ->4,线程0执行完毕后,并不是排在其后的线程1(获取锁的顺序)获取了锁,而是线程2获取了锁,说明synchronized是非公平锁;在各个线程执行的过程中,并没有其他线程能获得锁,说明synchronizd是独享锁
Java线程4-锁_第1张图片

2. ReentrantLock

ReentrantLock 是可重入锁、独享锁,并可通过构造函数构造公平锁/非公平锁,默认为非公平锁。
ReentrantLock 的 lock()/unlock() 操作通常与 try-finally 配合使用。

构造ReentrantLock为非公平锁

本示例可验证ReentrantLock 为可重入锁、独享锁,并默认为非公平锁(可通过构造函数修改)。
示例代码: ReentrantLockUnfairTest.java
代码运行效果如下图,从图中可看出: 在 ReentrantLock 修饰的get/set()方法中,线程从get()方法中顺利进入了set()方法中,说明 ReentrantLock 是可重入锁;各线程执行的过程中(enter–>leave),并没有其他线程在同时执行,说明 ReentrantLock 是独享锁;观察各线程获得锁的顺序(run): 0 -> 1 -> 2 -> 3 ->4,而线程的执行顺序却是: 0 -> 1 -> 2 -> 4 -> 3,在线程2执行完毕后,继续执行的是线程4,而不是线程3,线程未按获得获得锁执行,说明 默认的ReentrantLock 是非公平锁
Java线程4-锁_第2张图片

构造ReentrantLock为公平锁

本示例可验证ReentrantLock 为可重入锁、独享锁,并可通过构造函数构造非公平锁。
查看 ReentrantLock 的源码可发现,构造非公平锁,只需要向构造函数中传入true即可。

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

示例代码: ReentrantLockFairTest.java
代码运行结果如下图所示,从图中可看出: 在 ReentrantLock 修饰的get/set()方法中,线程从get()方法中顺利进入了set()方法中,说明 ReentrantLock 是可重入锁;各线程执行的过程中(enter–>leave),并没有其他线程在同时执行,说明 ReentrantLock 是独享锁;观察各线程获得锁的顺序(run): 0 -> 2 -> 1 -> 3 ->4,线程的执行顺序也是: 0 -> 2 -> 1 -> 3 -> 4,此时的线程执行顺序与获得锁的顺序一直,说明** ReentrantLock 可被构造为公平锁 **;
Java线程4-锁_第3张图片

3. ReentrantReadWriteLock

读写锁的性能都会比互斥锁要好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能提供比互斥锁更好的并发性和吞吐量。Java并发包提供的读写锁的具体实现是ReenantReadWriteLock。
ReentrantReadWriteLock是可重入锁、读写锁,并可通过构造函数构造公平锁(默认为非公平锁)。

特性 说明
公平性 支持公平锁/非公平锁,吞吐量还是非公平锁由于公平锁
可重入 该锁可重入;以读写线程为例:读线程在获取了读锁之后,能够再次获取读锁。而写线程在获取了写锁之后能够再次获取写锁,同时也可以获取读锁。
锁降级 遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级称为读锁。

参考文档

Java面试:多线程中的各种锁,你了解几个?
Java多线程中锁的理解与使用
JAVA锁有哪些种类,以及区别(转)
JAVA锁有哪些种类

相关面试题

Java有什么锁类型?

蚂蚁金服+拼多多+抖音+天猫Java面经合集,一次性查缺补漏个够

乐观锁和悲观锁了解吗?JDK中涉及到乐观锁和悲观锁的内容?

蚂蚁金服+拼多多+抖音+天猫Java面经合集,一次性查缺补漏个够

你可能感兴趣的:(Java)