1、显示锁的分类
不同的角度来讲,一般把显式锁分为以下几类:悲观锁和乐观锁、可重入锁和不可重入锁、公平锁和非公平锁、共享锁和独占锁、可中断锁和不可中断锁。
1.1、悲观锁和乐观锁
1.1.1、悲观锁
悲观锁 :每次进入临界区操作数据的时候,都认为别的线程会修改,所以线程每次在读写数据时都会上锁,锁住同步资源,这样其他线程需要读写这个数据时就会阻塞,一直等到拿到锁。悲观锁可以确保无论哪个线程持有锁,都能独占式访问临界区。
-
使用场景:
悲观锁适用于写多读少的场景,遇到高并发写时性能高。 synchronized 重量级锁就是一种典型的悲观锁。
传统的关系型数据库用到了很多悲观锁,比如行锁、表锁、读锁、写锁等。 -
使用悲观锁存在的问题:
① 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
② 一个线程持有锁后,会导致其他所有抢占此锁的线程挂起。
③ 如果一个优先级高的线程等待一个优先级低的线程释放锁,就会导致线程的优先级倒置,从而引发性能风险。
解决以上悲观锁的这些问题的有效方式是使用乐观锁去替代悲观锁。与之类似,数据库操作中的带版本号数据更新、JUC包的原子类,都使用了乐观锁的方式提升性能。
1.1.2、乐观锁
乐观锁 :每次去拿数据的时候,都认为别的线程不会修改,所以不会上锁。但是在更新的时候会判断一下,在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(跟上一次的版本号比较,如果一样就更新),如果失败就要重复读-比较-写的操作。乐观锁是一种思想,而CAS是这种思想的一种实现。
-
乐观锁的操作主要有两步:
① 冲突检测。
② 数据更新。
-
使用场景:
乐观锁适用于读多写少的场景,遇到高并发写时性能低。synchronized 轻量级锁 是一种典型的乐观锁
。 另外,JUC中基于抽象队列同步器(AQS)实现的显式锁(如ReentrantLock)都是乐观锁。
Java中的乐观锁基本都是通过 CAS 自旋操作实现的。CAS 是一种更新原子操作,比较当前值跟传入值是否一样,是则更新,不是则失败。在争用激烈的场景下,CAS 自旋会出现大量的空自旋,会导致乐观锁性能大大降低。
既然在争用激烈的场景下乐观锁的性能非常低,那么为什么JUC的显式锁都是乐观锁呢?因为 JUC 的显式锁都是基于 AQS 实现的,而 AQS 通过对队列的使用很大程度上减少了锁的争用,从而极大地减少了空的CAS自旋。所以,即使在争用激烈的场景下,基于 AQS 的 JUC 乐观锁也能表现出比悲观锁更佳的性能。
1.2、可重入锁和不可重入锁
1.2.1、可重入锁(递归锁)
可重入锁(递归锁):指的是一个线程可以多次抢占同一个锁。
JUC 的 ReentrantLock 类是可重入锁的一个标准实现类。
例如:线程 A 在进入外层函数抢占了一个 Lock 显式锁之后,当线程 A 继续进入内层函数时,如果遇到有抢占同一个 Lock 显式锁的代码,线程A依然可以抢到该 Lock 显式锁。
1.2.1、不可重入锁
不可重入锁 :指的是一个线程只能抢占一次同一个锁,不可重入锁与可重入锁正好相反。
例如:线程 A 在进入外层函数抢占了一个 Lock 显式锁之后,当线程 A 继续进入内层函数时,如果遇到有抢占同一个 Lock 显式锁的代码,线程A不可以抢到该 Lock 显式锁。除非线程 A 提前释放了该 Lock 显式锁,才能第二次抢占该锁。
1.3、公平锁和非公平锁
两种锁的执行流程
- 公平锁:
获取锁时,先将线程自己添加到等待队列的队尾并休眠,当某线程用完锁之后,会去唤醒等待队列中队首的线程尝试去获取锁,锁的使用顺序也就是队列中的先后顺序,在整个过程中,线程会从运行状态切换到休眠状态,再从休眠状态恢复成运行状态,但线程每次休眠和恢复都需要从用户态转换成内核态,而这个状态的转换是比较慢的,所以公平锁的执行速度会比较慢。
- 非公平锁:
当线程获取锁时,会先通过 CAS 尝试获取锁,如果获取成功就直接拥有锁,如果获取锁失败才会进入等待队列,等待下次尝试获取锁。这样做的好处是,获取锁不用遵循先到先得的规则,从而避免了线程休眠和恢复的操作,这样就加速了程序的执行效率。
1.3.1、公平锁
公平锁 :指不同的线程抢占锁的机会是公平的、平等的。从抢占时间上来说,先对锁进行抢占的线程,一定被先满足,抢锁成功的次序体现为FIFO(先进先出)顺序。简单来说,公平锁就是保障各个线程获取锁都是按照顺序来的,先到的线程先获取锁。
1.3.2、非公平锁
非公平锁 :指不同的线程抢占锁的机会是非公平的、不平等的,每个线程获取锁的顺序是随机的
。从抢占时间上来说,先对锁进行抢占的线程不一定被先满足,抢锁成功的次序不会体现为FIFO(先进先出)顺序。非公平锁的 优点在于吞吐量比公平锁大
,它的 缺点是有可能会导致线程优先级反转或者线程饥饿现象
。
synchronized 就是非公平锁,它选择要获得锁的线程是随机的,而不考虑等待时间的长短。默认情况下,ReentrantLock 实例也是非公平锁,但如果在实例构造时传入了参数true,所得到的锁就是公平锁。需要注意的是,ReentrantLock的tryLock()方法是一个特例,一旦有线程释放了锁,正在 tryLock 的线程就能优先取到锁,即使已经有其他线程在等待队列中。
1.3.3、代码示例
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 公平锁和非公平锁demo
*/
@Slf4j
public class LockDemo {
public static void main(String[] args) throws InterruptedException {
Sync sync = new Sync();
// 非公平锁,线程执行顺序随机
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
sync.buyTicket();
}, "非公平线程" + i).start();
}
TimeUnit.SECONDS.sleep(5);
System.out.println("--------------");
// 申明一个公平锁,Lock 默认是非公平锁,构造函数为true则为公平锁
Lock lock = new ReentrantLock(true);
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
log.info("{} -> 开始抢锁", Thread.currentThread().getName());
lock.lock();
sync.saleTicket();
lock.unlock();
}, "公平线程" + i).start();
}
}
}
@Slf4j
class Sync {
private Integer ticket = 0;
public Integer buyTicket() {
log.info("{} -> 开始抢锁", Thread.currentThread().getName());
synchronized (this) {
ticket++;
log.info("{} 执行-> ticket = {}", Thread.currentThread().getName(), ticket);
return ticket;
}
}
public Integer saleTicket() {
ticket--;
log.info("{} 执行-> ticket = {}", Thread.currentThread().getName(), ticket);
return ticket;
}
}
注意看打印结果,抢锁顺序和执行顺序。
执行结果:
11:21:25.326 [非公平线程6] INFO com.demo.Sync - 非公平线程6 -> 开始抢锁
11:21:25.332 [非公平线程6] INFO com.demo.Sync - 非公平线程6 执行-> ticket = 1
11:21:25.326 [非公平线程9] INFO com.demo.Sync - 非公平线程9 -> 开始抢锁
11:21:25.326 [非公平线程3] INFO com.demo.Sync - 非公平线程3 -> 开始抢锁
11:21:25.326 [非公平线程8] INFO com.demo.Sync - 非公平线程8 -> 开始抢锁
11:21:25.327 [非公平线程7] INFO com.demo.Sync - 非公平线程7 -> 开始抢锁
11:21:25.326 [非公平线程5] INFO com.demo.Sync - 非公平线程5 -> 开始抢锁
11:21:25.326 [非公平线程1] INFO com.demo.Sync - 非公平线程1 -> 开始抢锁
11:21:25.326 [非公平线程4] INFO com.demo.Sync - 非公平线程4 -> 开始抢锁
11:21:25.326 [非公平线程2] INFO com.demo.Sync - 非公平线程2 -> 开始抢锁
11:21:25.327 [非公平线程10] INFO com.demo.Sync - 非公平线程10 -> 开始抢锁
11:21:25.333 [非公平线程9] INFO com.demo.Sync - 非公平线程9 执行-> ticket = 2
11:21:25.335 [非公平线程2] INFO com.demo.Sync - 非公平线程2 执行-> ticket = 3
11:21:25.335 [非公平线程10] INFO com.demo.Sync - 非公平线程10 执行-> ticket = 4
11:21:25.335 [非公平线程4] INFO com.demo.Sync - 非公平线程4 执行-> ticket = 5
11:21:25.335 [非公平线程1] INFO com.demo.Sync - 非公平线程1 执行-> ticket = 6
11:21:25.335 [非公平线程5] INFO com.demo.Sync - 非公平线程5 执行-> ticket = 7
11:21:25.335 [非公平线程7] INFO com.demo.Sync - 非公平线程7 执行-> ticket = 8
11:21:25.336 [非公平线程8] INFO com.demo.Sync - 非公平线程8 执行-> ticket = 9
11:21:25.336 [非公平线程3] INFO com.demo.Sync - 非公平线程3 执行-> ticket = 10
--------------
11:21:30.330 [公平线程1] INFO com.demo.LockDemo - 公平线程1 -> 开始抢锁
11:21:30.330 [公平线程2] INFO com.demo.LockDemo - 公平线程2 -> 开始抢锁
11:21:30.330 [公平线程3] INFO com.demo.LockDemo - 公平线程3 -> 开始抢锁
11:21:30.330 [公平线程5] INFO com.demo.LockDemo - 公平线程5 -> 开始抢锁
11:21:30.330 [公平线程2] INFO com.demo.Sync - 公平线程2 执行-> ticket = 9
11:21:30.330 [公平线程4] INFO com.demo.LockDemo - 公平线程4 -> 开始抢锁
11:21:30.330 [公平线程6] INFO com.demo.LockDemo - 公平线程6 -> 开始抢锁
11:21:30.331 [公平线程3] INFO com.demo.Sync - 公平线程3 执行-> ticket = 8
11:21:30.331 [公平线程7] INFO com.demo.LockDemo - 公平线程7 -> 开始抢锁
11:21:30.332 [公平线程8] INFO com.demo.LockDemo - 公平线程8 -> 开始抢锁
11:21:30.332 [公平线程9] INFO com.demo.LockDemo - 公平线程9 -> 开始抢锁
11:21:30.333 [公平线程1] INFO com.demo.Sync - 公平线程1 执行-> ticket = 7
11:21:30.333 [公平线程10] INFO com.demo.LockDemo - 公平线程10 -> 开始抢锁
11:21:30.333 [公平线程5] INFO com.demo.Sync - 公平线程5 执行-> ticket = 6
11:21:30.333 [公平线程4] INFO com.demo.Sync - 公平线程4 执行-> ticket = 5
11:21:30.334 [公平线程6] INFO com.demo.Sync - 公平线程6 执行-> ticket = 4
11:21:30.336 [公平线程7] INFO com.demo.Sync - 公平线程7 执行-> ticket = 3
11:21:30.337 [公平线程8] INFO com.demo.Sync - 公平线程8 执行-> ticket = 2
11:21:30.337 [公平线程9] INFO com.demo.Sync - 公平线程9 执行-> ticket = 1
11:21:30.338 [公平线程10] INFO com.demo.Sync - 公平线程10 执行-> ticket = 0
Process finished with exit code 0
1.4、独占锁和共享锁
1.4.1、独占锁
独占锁 :指的是每次只有一个线程能持有的锁
。例如,JUC 的 ReentrantLock 类、Synchronized内置锁。
独占锁 也叫 排他锁、互斥锁、独享锁是一种 悲观保守的加锁策略
,它不必要地限制了读/读竞争,如果某个只读线程获取锁,那么其他的读线程都只能等待,这种情况下就限制了读操作的并发性,因为读操作并不会影响数据的一致性。
1.4.2、共享锁
共享锁:允许多个线程同时获取锁,容许线程并发进入临界区
。例如,JUC的ReentrantReadWriteLock(读写锁)类、JUC中的 Semaphore(信号量)、CountDownLatch倒数闩。使用该读写锁时,读操作可以有很多线程一起读,但是写操作只能有一个线程去写,而且在写入的时候,别的线程也不能进行读的操作。
与独占锁不同,共享锁是一种乐观锁,它放宽了加锁策略,并不限制读/读竞争,允许多个执行读操作的线程同时访问共享资源
。
用 ReentrantLock 锁替代 ReentrantReadWriteLock 锁虽然可以保证线程安全,但是也会浪费一部分资源,因为多个读操作并没有线程安全问题,所以在读的地方使用读锁,在写的地方使用写锁,可以提高程序执行效率。
我们 JUC 常用的共享锁 Semaphore,具体可参考本人博客: 《Java 并发编程 - 共享锁 Semaphore》
以及常用的共享锁 CountDownLatch -> 《Java 并发编程 - CountDownLatch》
1.5、读写锁
接下来聊聊基于共享锁和独占锁基础上的一种组合锁:读写锁。与单一的互斥锁相比,组合起来的读写锁允许对于共享数据进行更大程度的并发操作。虽然每次只能有一个写线程,但是同时可以有多个线程并发地读数据。读写锁适用于读多写少的并发情况。
读写锁的内部包含两把锁:一把是 读(操作)锁
,是一种 共享锁
;另一把是 写(操作)锁
,是一种 独占锁
。在没有写锁的时候,读锁可以被多个线程同时持有。写锁是具有排他性的:如果写锁被一个线程持有,其他的线程不能再持有写锁,抢占写锁会阻塞;如果写锁被一个线程持有,其他的线程不能再持有读锁,抢占读锁也会阻塞。
读写锁的读写操作之间的互斥原则如下:
- 读操作、读操作能共存,是相容的。
- 读操作、写操作不能共存,是互斥的。
- 写操作、写操作不能共存,是互斥的。
JUC包中的读写锁接口为ReadWriteLock,通过ReadWriteLock接口能获取其内部的两把锁:一把是ReadLock,负责读操作;另一把是WriteLock,负责写操作。JUC中ReadWriteLock接口的实现类为ReentrantReadWriteLock。
1.5.1、ReadWriteLock 接口的实现类 ReentrantReadWriteLock
- ReentrantLock虽然可以灵活地实现线程安全,但是他是一种完全互斥锁,即某一时刻永远只允许一个线程访问共享资源,不管是读数据的线程还是写数据的线程。这导致的结果就是,效率低下。ReentrantReadWriteLock 类很好的解决了该问题。
- ReentrantReadWriteLock 类实现了 ReadWriteLock 接口,而 ReadWriteLock 接口中维护了两个锁:读锁(共享锁)和写锁(排他锁)。
- ReentrantReadWriteLock 允许线程同时读取共享资源;但是如果有一个线程是写数据,那么其他线程就不能去读写该资源。即会出现三种情况:读读共享,写写互斥,读写互斥。
锁升级是指读锁升级为写锁,锁降级指的是写锁降级为读锁。在 ReentrantReadWriteLock 读写锁中,只支持写锁降级为读锁,而不支持读锁升级为写锁
。
ReentrantReadWriteLock 主要特性 公平性 支持公平锁和非公平锁,默认是非公平锁,可以根据构造方式设置公平锁。
// 构造函数默认是false 非公平锁,由于读线程之间没有锁竞争,所以读操作,没有公平性和非公平性。
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);
ReentrantReadWriteLock 实现类相关方法说明
- 例子
package com.pzf.lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
* <p>
* ReadWriteLock 实现类 ReentrantReadWriteLock
* <p>
* 读-读 可以共存
* 读-写 不能共存
* 写-写 不能共存
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
// 普通写
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {myCache.put(temp + "", temp + "");}, String.valueOf(i)).start();
}
// 普通读
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {myCache.get(temp + "");}, String.valueOf(i)).start();
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
MyLockCache myLockCache = new MyLockCache();
// 读写锁 -> 写
for (int i = 6; i <= 10; i++) {
final int temp = i;
new Thread(() -> {myLockCache.put(temp + "", temp + "");}, String.valueOf(i)).start();
}
// 读写锁 -> 读
for (int i = 6; i <= 10; i++) {
final int temp = i;
new Thread(() -> {myLockCache.get(temp + "");}, String.valueOf(i)).start();
}
}
}
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "写入成功");
}
public Object get(String key) {
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始读");
Object value = map.get(key);
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "读成功");
return value;
}
}
class MyLockCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "写入成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public Object get(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始读");
Object value = map.get(key);
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "读成功");
return value;
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
return null;
}
}
- 执行结果:
2-> key = 2开始写入
2-> key = 2写入成功
1-> key = 1开始写入
5-> key = 5开始写入
5-> key = 5写入成功
3-> key = 3开始写入
3-> key = 3写入成功
4-> key = 4开始写入
4-> key = 4写入成功
1-> key = 1开始读
1-> key = 1读成功
1-> key = 1写入成功
2-> key = 2开始读
2-> key = 2读成功
3-> key = 3开始读
4-> key = 4开始读
4-> key = 4读成功
5-> key = 5开始读
3-> key = 3读成功
5-> key = 5读成功
7-> key = 7开始写入
7-> key = 7写入成功
10-> key = 10开始写入
10-> key = 10写入成功
6-> key = 6开始写入
6-> key = 6写入成功
8-> key = 8开始写入
8-> key = 8写入成功
9-> key = 9开始写入
9-> key = 9写入成功
6-> key = 6开始读
6-> key = 6读成功
10-> key = 10开始读
8-> key = 8开始读
7-> key = 7开始读
8-> key = 8读成功
9-> key = 9开始读
9-> key = 9读成功
10-> key = 10读成功
7-> key = 7读成功
1.6、可中断锁和不可中断锁
在抢锁过程中能通过某些方法终止抢占过程,这就是可中断锁,否则就是不可中断锁。Java 的 synchronized 内置锁就是一个不可中断锁,而JUC的显式锁(如 ReentrantLock)是一个可中断锁。
-
可中断锁 :如果某一线程A正占有锁在执行临界区代码,另一线程B正在阻塞式抢占锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己的阻塞等待,这种就是可中断锁。
-
不可中断锁 :一旦这个锁被其他线程占有,如果自己还想抢占,只能选择等待或者阻塞,直到别的线程释放这个锁,如果别的线程永远不释放锁,那么自己只能永远等下去,并且没有办法终止等待或阻塞。
2、关于 Lock 接口
Lock 是 java.util.concurrent (JUC) 包中的一个接口类,JUC中的显式锁都实现了Lock接口。
锁是一种通过多个线程控制对共享资源的访问的工具。通常,一个锁提供对共享资源的独占访问:在一个时间只有一个线程可以获得锁和所有访问共享资源,需要先获得锁。然而,有些锁可以允许并发访问共享资源,如一个ReadWriteLock读锁。
Lock 和 Synchronized 都可以达到锁的目的,不过 Lock 有更灵活的结构,有完全不同的特性,可以支持多个相关的 Condition对象
3、 Lock 和 Synchronized
- 不加锁
package com.pzf.suo;
import lombok.AllArgsConstructor;
import java.util.concurrent.TimeUnit;
public class UnSync {
public static void main(String[] args) {
SaleTickets saleTicket = new SaleTickets(2);
new Thread(() -> saleTicket.sale(), "A").start();
new Thread(() -> saleTicket.sale(), "B").start();
new Thread(() -> saleTicket.sale(), "C").start();
}
}
@AllArgsConstructor
class SaleTickets {
private int num;
public void sale() {
if (num > 0) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"-> 当前票数" + (num--)+",剩余票数" + num);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
运行结果:
C-> 当前票数0,剩余票数-1
A-> 当前票数1,剩余票数0
B-> 当前票数2,剩余票数0
- Synchronized 锁
package com.pzf.suo;
import lombok.AllArgsConstructor;
import java.util.concurrent.TimeUnit;
public class ToSync {
public static void main(String[] args) {
SaleTickets1 saleTicket = new SaleTickets1(2);
new Thread(() -> saleTicket.sale(), "A").start();
new Thread(() -> saleTicket.sale(), "B").start();
new Thread(() -> saleTicket.sale(), "C").start();
}
}
@AllArgsConstructor
class SaleTickets1 {
private int num;
// synchronized 修饰非静态方法,是对调用该方法的对象加锁,俗称"对象锁"
public synchronized void sale() {
if (num > 0) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"-> 当前票数" + (num--)+",剩余票数"+num);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
运行结果:
A-> 当前票数2,剩余票数1
C-> 当前票数1,剩余票数0
- Lock 锁
package com.pzf.suo;
import lombok.NoArgsConstructor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ToLock {
public static void main(String[] args) {
SaleTickets2 saleTicket = new SaleTickets2(5);
for (int i = 0; i < 10; i++) {
new Thread(() -> saleTicket.sale(), i + "").start();
}
}
}
@NoArgsConstructor
class SaleTickets2 {
private int num;
private Lock lock = new ReentrantLock();
public SaleTickets2(int num) {
this.num = num;
}
public void sale() {
lock.lock();
try {
if (num > 0) {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"-> 当前票数"+(num--)+",剩余票数" + num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 手动释放锁
lock.unlock();
}
}
}
运行结果:
0-> 当前票数5,剩余票数4
2-> 当前票数4,剩余票数3
1-> 当前票数3,剩余票数2
3-> 当前票数2,剩余票数1
4-> 当前票数1,剩余票数0
3.1、Lock 和 Synchronized 区别
- Lock 是 java.util.concurrent (JUC) 包中的一个类,Synchronized 是一个内置的 java 关键字。
- Lock 可以判断是否获取到了锁,Synchronized 无法判断获取锁的状态。
- Lock 必须要手动释放锁,如果不释放锁会产生死锁。Synchronized 会自动释放锁。
- Lock 是可重入锁,默认为非公平锁,但是可以设置为公平锁。 Synchronized 可重入锁,不可中断的非公平锁。
- Lock 线程1(获得锁,阻塞)、线程2(不一定等待)。Synchronized 线程2 会等待下去。
- Lock 适合锁大量的同步代码,Synchronized 适合少量的代码同步问题。
4、Lock 接口的实现类 ReentrantLock
ReentrantLock 重入锁和 synchronize 关键字一样,是互斥锁。比synchronize关键字更加灵活。
ReentrantLock 默认为非公平锁,构造函数传 true 时,可设置为公平锁。
ReentrantLock 实现类相关方法说明
线程系列博文:
线程系列 1 - 线程基础
线程系列 2 - 并发编程之线程池 ThreadPool 的那些事
线程系列 3 - 关于 CompletableFuture
线程系列 4 - synchronized 和线程间的通信
线程系列 5 - CAS 和 JUC原子类
线程系列 7 - JUC高并发容器类
.