Java11中的Lock接口提供
lock()
和lockInterruptibly()
两种锁定方法,用于获取锁,但处理线程中断时有所不同,lock()
使线程等待直到锁释放,期间无视中断;而lockInterruptibly()
在等待中若收到中断请求,会立即响应并抛出异常,这种机制使lockInterruptibly()
更灵活,能更好处理线程中断,避免无意义等待,提升系统响应性和效率。
定义
在Java 11中,Lock
接口提供了两种锁定方法:lock()
和lockInterruptibly()
,这两种方法虽然都是为了获取锁,但在处理线程中断时有着显著的不同。
lock()
方法是一种基本的锁定机制,当一个线程调用这个方法时,如果锁已经被其他线程持有,那么调用线程就会进入等待状态,直到锁被释放,在等待的过程中,这个线程会无视中断请求,也就是说,即使有其他线程调用了这个等待线程的interrupt()
方法,它也不会有任何响应,依旧会“执着”地等待锁的释放。
而lockInterruptibly()
方法则不同,它提供了一种更加“灵活”的锁定机制,当一个线程调用这个方法时,如果锁已经被其他线程持有,那么这个线程同样会进入等待状态,但是,在等待的过程中,如果这个线程收到了中断请求(即有其他线程调用了它的interrupt()
方法),那么它就会立即响应中断,不再等待锁的释放,而是抛出一个InterruptedException
异常。
举个例子来说明这两种方法的区别,假设有一个仓库,只有一把钥匙可以打开(这里把钥匙比作锁),现在有两个人(线程)都想进入仓库取东西,
- 如果第一个人使用
lock()
方法,那么在他拿到钥匙并进入仓库之前,即使有人叫他(发送中断请求),他也不会理睬,直到他取完东西出来并把钥匙交给下一个人。 - 如果第一个人使用
lockInterruptibly()
方法,那么在他等待钥匙的时候,如果有人叫他(发送中断请求),他就会立即响应,放弃等待,转而去做其他事情。
lockInterruptibly()
方法就提供了一种更加“人性化”的锁定机制,能够更好地处理线程的中断请求,避免线程长时间无意义地等待锁的释放。
代码案例
使用lock()
Lock
接口通过其实现类ReentrantLock
来使用,以下是一个使用ReentrantLock
和lock()
方法的示例代码,该代码模拟了上面描述的“仓库”案例,在这个例子中,有一个Warehouse
类,它包含一个资源,并且使用ReentrantLock
来控制对这个资源的访问,如下代码:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 仓库类,使用Lock来控制访问
public class Warehouse {
private final Lock lock = new ReentrantLock();
private String resource = "Goods inside the warehouse"; // 仓库中的资源
// 获取仓库资源的方法
public void accessResource() {
lock.lock(); // 获取锁
try {
// 模拟长时间的操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 注意:这里不会响应中断,因为使用了lock()方法
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " accessed the resource: " + resource);
} finally {
lock.unlock(); // 释放锁
}
}
}
// 客户端代码,模拟两个线程同时尝试访问仓库资源
public class Client {
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
// 创建并启动两个线程,它们将尝试访问仓库资源
Thread thread1 = new Thread(() -> warehouse.accessResource(), "Thread-1");
Thread thread2 = new Thread(() -> warehouse.accessResource(), "Thread-2");
thread1.start();
thread2.start();
// 注意:由于使用了lock()方法,即使尝试中断线程,它们也不会响应,直到它们自然地完成操作
}
}
在上面代码中,Warehouse
类有一个accessResource
方法,它使用lock()
方法获取锁,并在finally
块中释放锁,以确保锁总是会被释放,无论是否发生异常,Client
类创建了两个线程,它们几乎同时尝试访问Warehouse
的资源,由于lock()
方法不会响应中断,因此即使一个线程在持有锁时被中断,它也会继续执行直到释放锁。
这个代码输出内容如下,因为ReentrantLock
确保了资源在任何时候只被一个线程访问,如下:
Thread-1 accessed the resource: Goods inside the warehouse
Thread-2 accessed the resource: Goods inside the warehouse
使用lockInterruptibly()
下面使用Lock
接口中的lockInterruptibly()
方法的Java代码示例,演示如何中断一个正在等待获取锁的线程,如下代码:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 仓库类,使用Lock来控制访问
public class Warehouse {
private final Lock lock = new ReentrantLock();
private String resource = "Goods inside the warehouse"; // 仓库中的资源
// 可响应中断地获取仓库资源的方法
public void accessResourceInterruptibly() throws InterruptedException {
lock.lockInterruptibly(); // 可响应中断地获取锁
try {
// 模拟长时间的操作
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " accessed the resource: " + resource);
} finally {
lock.unlock(); // 无论如何都会释放锁
}
}
}
// 客户端代码,模拟两个线程同时尝试访问仓库资源,并中断其中一个线程
public class Client {
public static void main(String[] args) throws InterruptedException {
Warehouse warehouse = new Warehouse();
// 创建第一个线程并启动,它将尝试访问仓库资源
Thread thread1 = new Thread(() -> {
try {
warehouse.accessResourceInterruptibly();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted while waiting for the lock.");
}
}, "Thread-1");
thread1.start();
// 让第一个线程有时间去获取锁
Thread.sleep(500);
// 创建第二个线程,它也将尝试访问仓库资源,但会中断它
Thread thread2 = new Thread(() -> {
try {
warehouse.accessResourceInterruptibly();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted while waiting for the lock.");
}
}, "Thread-2");
thread2.start();
// 中断第二个线程,因为它可能正在等待获取锁
Thread.sleep(500);
thread2.interrupt();
// 等待第一个线程完成
thread1.join();
}
}
在上面代码中,Warehouse
类有一个accessResourceInterruptibly
方法,它使用lockInterruptibly()
方法来获取锁,如果线程在等待获取锁的过程中被中断,它将抛出InterruptedException
异常。
在Client
类的main
方法中,创建了两个线程,第一个线程(thread1
)启动后,让它先运行一段时间以确保它能够获取到锁,然后,启动第二个线程(thread2
),并立即中断它,由于thread2
在调用lockInterruptibly()
时可能会被中断,因此它应该捕获InterruptedException
并打印出一条消息,表明它在等待锁的过程中被中断了。
输出如下:
Thread-1 accessed the resource: Goods inside the warehouse
Thread-2 was interrupted while waiting for the lock.
这个输出表明thread1
成功访问了资源,而thread2
则在等待获取锁的过程中被中断了。
核心总结
1、使用lock方法的情况:
当需要确保一段代码或一系列操作以原子方式执行,即在这段代码执行期间不会被其他线程干扰时,可以使用lock方法,lock方法会尝试获取锁,如果锁当前被其他线程持有,那么当前线程将被挂起(即阻塞),直到锁变得可用,这是一种基本的线程同步机制,用于防止多个线程同时访问共享资源,从而避免数据不一致和其他并发问题。
2、使用lockInterruptibly方法的情况:
与lock方法类似,lockInterruptibly方法也用于获取锁以保护临界区,但是,lockInterruptibly有一个额外的特性:它是可响应中断的,这意味着,如果当前线程在等待获取锁的过程中被中断(例如,通过调用Thread.interrupt方法),lockInterruptibly将不会无限期地等待下去,而是会立即抛出InterruptedException异常,这使得线程能够更灵活地处理中断,例如,可以在被中断时执行一些清理操作或通知其他线程。
3、总结:
如果需要确保代码块的原子性,并且不关心线程是否能够在等待锁时被中断,那么可以使用lock方法,但是,如果希望线程在等待锁时能够响应中断,那么应该使用lockInterruptibly方法。在处理可能被中断的长时间操作或需要更细粒度的中断控制时,lockInterruptibly通常是一个更好的选择。