ReentrantLock实现类(Lock接口)详解:【Java】Lock锁接口和实现类详解
synchronized关键字线程同步详解:【Java】线程的基本同步方式和常用方法
1. 两者优劣特点对比(详细)
比较点 | sychronized关键字 | ReentrantLock实现类(Lock接口) |
---|---|---|
构成 | 它是java语言的关键字,是原生语法层面的互斥,需要jvm实现 | 它是JDK 1.5之后提供的API层面的互斥锁类 |
实现 | 通过JVM获取锁/释放锁 | api层面的获取锁释放锁,释放锁需要手动 |
代码 | 采用synchronized不需要手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用,更安全 | ReentrantLock则必须要手动释放锁,如果没有主动释放锁,就有可能导致出现死锁,需要lock()和unlock()方法配合try-finally语句块完成 |
灵活 | 锁的范围是整个方法或synchronized代码块部分 | 使用Lock接口的方法调用,可以跨方法,灵活性更强大 |
中断 | 不可中断,除非抛出异常(释放锁方式: ①代码执行完,正常释放锁; ②抛出异常,由JVM退出等待) |
可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待(方法: ①设置超时方法 tryLock(long timeout, TimeUnit unit)时间过了就放弃等待; ②lockInterruptibly()放代码块中,调用interrupt()方法可中断) |
公平 | 非公平锁,不考虑排队问题直接尝试获取锁 | 公平锁和非公平锁两者都可以,默认非公平锁,检查是否有排队等待的线程,先来者先得锁,构造器可以传入boolean值,true为公平锁,false为非公平锁 |
条件 | 无 | 通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能 |
高级 | 无 | 提供很多方法用来监听当前锁的信息,如: getHoldCount() getQueueLength() isFair() isHeldByCurrentThread() isLocked() |
便利 | 方便简洁,由编译器去保证锁的加锁和释放 | 需要手工声明来加锁和释放锁 |
场景 | 资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronized,另外可读性非常好 | 提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步等 |
性能 | 低并发优先,性能好,可读性好 | 高并发优先,性能最佳 |
2. ReentrantLock 的3个高级功能
由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了3个高级功能(表格中也有锁体现)。
功能①:等待可中断
持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。
三个API说明:
1)lock(), 拿不到lock就不罢休,不然线程就一直block,死等。
2)tryLock(),马上返回,拿到lock就返回true,不然返回false,不等。带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false。
3)lockInterruptibly():
- 线程在sleep或wait,join此时如果别的进程调用此进程的 interrupt()方法,此线程会被唤醒并被要求处理InterruptedException;
- 此线程在运行中,则不会收到提醒。但是此线程的 “打扰标志”会被设置, 可以通过isInterrupted()查看并作出处理。线程在请求lock并被阻塞时,如果被interrupt,则“此线程会被唤醒并被要求处理InterruptedException”。并且如果线程已经被interrupt,再使用lockInterruptibly的时候,此线程也会被要求处理interruptedException。
代码示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestInterrupt {
public static void main(String[] args) throws Exception {
new TestInterrupt().test();
}
public void test() throws Exception {
final Lock lock = new ReentrantLock();
lock.lock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
int i = 5;
while (i-- > 0) {
System.out.println("I'm running " + i);
Thread.sleep(1000);
}
// 相当于一个可被中断的设置,会抛出中断异常(受查异常)
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted(我被中断了).");
System.out.println("被打断后所做的事情...");
}
}
}, "子线程-1");
t1.start();
Thread.sleep(3000); // 2秒后打断t1线程,t1线程会中断执行
t1.interrupt(); // 打断:触发t1线程任务run中的中断异常(抛出),进入执行异常代码块
}
}
释义:线程中断(interrupt)
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)。
- 调用 interrupt()方法并不会中断一个正在运行的线程。也就是说处于 Running 状态的线程并不会因为被中断而被终止,仅仅改变了内部维护的中断标识位而已。
- 若调用 sleep()而使线程处于 TIMED-WATING 状态,这时调用 interrupt()方法,会抛出InterruptedException,从而使线程提前结束 TIMED-WATING 状态。
- 许多声明抛出 InterruptedException 的方法(如 Thread.sleep(long mills 方法)),抛出异常前,都会清除中断标识位,所以抛出异常后,调用 isInterrupted()方法将会返回 false。
- 中断状态是线程固有的一个标识位,可以通过此标识位安全的终止线程。比如,你想终止一个线程 thread 的时候,可以调用 thread.interrupt()方法,在线程的 run 方法内部可以根据 thread.isInterrupted()的值来优雅的终止线程。
功能②:公平锁机制
多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
公平锁:加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得;
非公平锁:加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。
公平锁、非公平锁的创建方式:
//创建一个非公平锁,默认是非公平锁
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);
//创建一个公平锁,构造传参true
Lock lock = new ReentrantLock(true);
功能③:锁绑定多个条件
一个ReentrantLock对象可以同时绑定对个对象。ReentrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
代码示例:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class TestCondition {
public static void main(String[] args) throws InterruptedException {
MyService service = new MyService();
Thread t1 = new Thread(new MyServiceThread1(service), "a");
Thread t2 = new Thread(new MyServiceThread2(service), "b");
t1.start();
t2.start();
// 线程sleep2秒钟
Thread.sleep(2000);
// 唤醒所有持有conditionA的线程
service.signallA();
Thread.sleep(3000);
// 唤醒所有持有conditionB的线程
service.signallB();
}
}
// MyServiceThread1 使用了awaitA()方法,持有的是conditionA!
class MyServiceThread1 implements Runnable {
private MyService service;
public MyServiceThread1(MyService service) {
this.service = service;
}
@Override
public void run() {
service.awaitA();
}
}
// MyServiceThread2 使用了awaitB()方法,持有的是conditionB!
class MyServiceThread2 implements Runnable {
private MyService service;
public MyServiceThread2(MyService service) {
this.service = service;
}
@Override
public void run() {
service.awaitB();
}
}
// 主要的功能代码
class MyService {
// 实例化一个ReentrantLock对象
private ReentrantLock lock = new ReentrantLock();
// 为线程A注册一个Condition
public Condition conditionA = lock.newCondition();
// 为线程B注册一个Condition
public Condition conditionB = lock.newCondition();
public void awaitA() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入了awaitA方法");
long timeBefore = System.currentTimeMillis();
conditionA.await(); // 执行conditionA等待
long timeAfter = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "被唤醒");
System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore) / 1000 + "s");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void awaitB() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入了awaitB方法");
long timeBefore = System.currentTimeMillis();
conditionB.await(); // 执行conditionB等待
long timeAfter = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "被唤醒");
System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore) / 1000 + "s");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signallA() {
try {
lock.lock();
System.out.println("启动唤醒程序");
// 唤醒所有注册conditionA的线程
conditionA.signalAll();
} finally {
lock.unlock();
}
}
public void signallB() {
try {
lock.lock();
System.out.println("启动唤醒程序");
// 唤醒所有注册conditionB的线程
conditionB.signalAll();
} finally {
lock.unlock();
}
}
}
补充:Condition 类和 Object 类锁方法区别
- Condition 类的 awiat 方法和 Object 类的 wait 方法等效;
- Condition 类的 signal 方法和 Object 类的 notify 方法等效;
- Condition 类的 signalAll 方法和 Object 类的 notifyAll 方法等效;
- ReentrantLock 类可以唤醒指定条件的线程,而 Object 的唤醒是随机的。
补充:trylock 和 lock 和 lockInterruptibly 方法区别
- tryLock 能获得锁就返回 true,不能就立即返回 false,tryLock(long timeout,TimeUnit unit),可以增加时间限制,如果超过该时间段还没获得锁,返回 false;
- lock 能获得锁就返回 true,不能的话一直等待获得锁;
- lock 和 lockInterruptibly,如果两个线程分别执行这两个方法,但此时中断这两个线程,lock 不会抛出异常,而 lockInterruptibly 会抛出异常。
3. 什么时候使用ReentrantLock?
答案是:
- 原子操作的颗粒度更小的加锁操作或跨方法释放锁时(灵活);
- 如果你需要使用ReentrantLock的三个高级功能时。