目录
1 前言
本人jdk版本8。
在Lock接口出现之前,java程序靠的是synchronized关键字实现共享锁功能的,而Java SE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能。Concurrent包中已经提供的实现了Lock接口的锁包括:ReentrantLock(可重入锁)和ReadWriteLock(读写锁)等。其提供的接口方法如下:
public interface Lock {
// 获取锁,获取不到则将当前线程加入阻塞队列
void lock();
// 响应中断的获取锁
void lockInterruptibly() throws InterruptedException;
// 尝试一次获取锁的动作
boolean tryLock();
// 有时间限制的获取锁
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放当前线程持有的锁
void unlock();
}
它提供了synchronized关键字类似的同步功能,只是在使用是需要显示的获取和释放锁,虽然他缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁的多种synchronized关键字锁不具备的同步特性。官方给出的使用方法如下:
Lock lock = new ReentrantLock();
lock.lock();
try{
// synchronized block
}finally{
lock.unLock();
}
2 synchronized与Lock的区别
2.1 锁的获取与释放操作
- synchronized,线程执行到加锁区域会自动获取锁,当加锁区域执行完或执行中抛出异常会自动释放锁,程序猿不用也不能控制锁的获取与释放。
- Lock,程序猿要手动进行加锁与释放,如果忘记释放锁则可能造成死锁。
2.2 锁状态的可知性
- synchronized,参与锁竞争的线程不知道锁是否已被获取,也不知道获取锁的线程是不是自己。
- Lock,通过同步器的getState()能知道锁的获取情况,通过getExclusiveOwnerThread()也能知道独占锁的是哪个线程。
2.3 灵活性
- synchronized,线程获取锁失败只能阻塞,等到拥有锁的线程释放锁才有可能被唤醒,不能响应中断。
- Lock,提供了三类方法来对锁进行灵活的获取,获取失败也可以使线程不陷入阻塞,就算阻塞了也能通过中断阻塞线程来取消阻塞。具体见下:
操作 | 描述 |
tryLock() | 尝试获取一次锁。通过锁的状态来判断是否能获取成功,true则获取到锁,false则返回。 |
lockInterruptibly() | 可中断的获取锁,该方法必须显示的处理(如catch)可能抛出的InterruptedException。若获取锁失败该线程会被加入阻塞队列,并一直检测线程的中断状态,若线程被中断,则会将该线程从阻塞队列中移除并抛出InterruptedException,接下来该线程会从catich代码块继续向下执行。若线程已经获取了锁,则中断除了将线程标志置为true以外不会产生任何影响。(详情见下面代码测试) |
tryLock(long time, TimeUnit unit) | 在lockInterruptibly()的基础上增加了时间限制,若在指定时间内还没获取到锁则返回false并退出阻塞,从下一行代码接着执行。 |
3 lockInterruptibly()中断测试
因为InterruptedException不是RuntimeException,所以lockInterruptibly()方法必须显示的处理可能抛出来的InterruptedException。下面代码对lockInterruptibly()进行了catch包围。
1)线程获取锁失败阻塞时被中断
下面开启了两个线程,用同一个ReentrantLock来进行同步。线程1(也可能是线程2)首先执行,获取到锁,进入try区域,执行无限循环;线程2接着执行,因为线程1已经获取到锁,所以陷入阻塞。线程2阻塞后,在main线程中对线程2进行了中断。
public class Test1 {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程1");
MyThread mt2 = new MyThread("线程2");
mt1.start();
mt2.start();
while (true){
if (MyThread.lock.hasQueuedThread(mt2)){
System.out.println(mt2.getName()+"进入阻塞队列");
mt2.interrupt();
System.out.println("中断了"+mt2.getName());
break;
}
}
}
}
class MyThread extends Thread{
static ReentrantLock lock = new ReentrantLock();
MyThread(String name){ super(name); }
@Override
public void run(){
try {
lock.lockInterruptibly();
System.out.println(Thread.currentThread().getName()+"进入了lock后面的try区域");
while (true){}
}catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+":是否在阻塞队列中——"+lock.hasQueuedThread(Thread.currentThread()));
e.printStackTrace();
}finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
打印信息如下。当线程获取锁失败而加入阻塞队列后,若此时被其它线程中断,该方法会抛出InterruptedException,同时将此线程的节点从阻塞队列移除,该线程会接着执行catch下面的代码。注意一下上面代码的finally块,在unlock()之前我作了个判断,判断当前节点是否是这个锁的持有者,是才unlock,否则线程2被中断后继续执行到unlock会抛出IllegalMonitorStateException,因为释放锁必须要当前线程是锁的持有者。
线程1进入了lock后面的try区域
线程2进入阻塞队列
中断了线程2
线程2:是否在阻塞队列中——false
java.lang.InterruptedException at ....(此处省略异常打印信息 )
...(两个线程无限循环执行)
2)线程持有锁时被中断
启动一个线程,线程进行无限循环的判断,判断自己是否被中断,是就退出循环。
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyThread mt1 = new MyThread("线程1");
mt1.start();
Thread.sleep(2000);
mt1.interrupt();
}
}
class MyThread extends Thread{
static ReentrantLock lock = new ReentrantLock();
MyThread(String name){ super(name); }
@Override
public void run(){
try {
lock.lockInterruptibly();
while (true){
if (Thread.currentThread().isInterrupted()){
System.out.println("线程已被中断");
break;
}
}
System.out.println("被中断了我还在执行");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
打印信息如下。显然,持有锁的线程被中断只会将中断标志置为true,而不会使线程释放锁,更不会中断线程的执行。
线程已被中断
被中断了我还在执行
Process finished with exit code 0
wait()和join()被中断的过程参考:https://blog.csdn.net/qq_34039868/article/details/105103430。