Lock接口
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock();
Condition newCondition();
}
这 5 种方法分别是 lock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()、 unlock()。
lock()
在线程获取锁的时如果锁被其它的线程获取了,就会进行等待,lock
加锁和释放锁都必须以代码的形式写出来,是由我们自己释放锁。要有try
和catch
语 句,在try
中获取资源,如果出现异常在catch
中捕获,然后释放锁。
伪代码使用如下:
Lock lock = ...; lock.lock();
try{
}finally{ lock.unlock();
}
一定不要忘记在finally
语句中释放锁,否则是非常危险的锁会得不到释放,一旦陷入死锁就 会造成很大的隐患。
tryLock()
它可以尝试获取当前的锁,如果其他线程没有被占用,就能获取成功,返回true
,反之亦然。
为代码如下:
Lock lock = ...;
if(lock.tryLock()) {
try{//业务逻辑
}finally{ lock.unlock();
}
}else {}
有了这个强大的 tryLock()
方法我们便可以解决死锁问题
如下面的例子:
死锁是多线程编程中一种比较复杂的问题,而使用锁可以一定程度上避免死锁的发生。下面是一个简单的Java Lock锁解决死锁的例子,使用ReentrantLock
和tryLock
方法来避免死锁。
考虑两个资源ResourceA
和ResourceB
,两个线程分别需要访问这两个资源。如果线程1先获取ResourceA
,然后尝试获取ResourceB
,而线程2先获取ResourceB
,然后尝试获取ResourceA
,就可能发生死锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class DeadlockExample {private static class SharedResource {final Lock lock = new ReentrantLock();}private static class MyThread extends Thread {private final SharedResource resource1;private final SharedResource resource2;public MyThread(String name, SharedResource resource1, SharedResource resource2) {super(name);this.resource1 = resource1;this.resource2 = resource2;}@Overridepublic void run() {try {// 尝试获取第一个资源resource1.lock.lock();System.out.println(getName() + " acquired lock on " + resource1);// 等待一段时间模拟处理业务逻辑Thread.sleep(100);// 尝试获取第二个资源if (resource2.lock.tryLock()) {try {System.out.println(getName() + " acquired lock on " + resource2);// 执行业务逻辑} finally {resource2.lock.unlock();System.out.println(getName() + " released lock on " + resource2);}} else {System.out.println(getName() + " couldn't acquire lock on " + resource2 + ". Releasing lock on " + resource1);}} catch (InterruptedException e) {e.printStackTrace();} finally {resource1.lock.unlock();System.out.println(getName() + " released lock on " + resource1);}}}public static void main(String[] args) {SharedResource resourceA = new SharedResource();SharedResource resourceB = new SharedResource();// 创建两个线程,分别尝试获取不同的资源MyThread thread1 = new MyThread("Thread-1", resourceA, resourceB);MyThread thread2 = new MyThread("Thread-2", resourceB, resourceA);// 启动两个线程thread1.start();thread2.start();}
}
运行结果:
Thread-1 acquired lock on SharedResource@hash1
Thread-1 acquired lock on SharedResource@hash2
Thread-2 couldn't acquire lock on SharedResource@hash1. Releasing lock on SharedResource@hash2
Thread-1 released lock on SharedResource@hash2
Thread-1 released lock on SharedResource@hash1
Thread-2 acquired lock on SharedResource@hash2
Thread-2 acquired lock on SharedResource@hash1
Thread-2 released lock on SharedResource@hash1
Thread-2 released lock on SharedResource@hash2
这个输出说明线程1首先获取resource1
的锁,然后等待一段时间,尝试获取resource2
的锁。由于线程2已经获取了resource2
的锁,线程1无法立即获取resource2
的锁,因此释放了resource1
的锁。随后,线程2成功获取了resource1
的锁,完成了整个流程。
tryLock(long time, TimeUnit unit)
该方法可以设置超时时间,如果拿不到锁等待超了设置的时间后,线程就会放弃获取这把 锁。在等待的时间也可以中断线程,避免死锁的发生。
如下面的例子:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;public class TryLockExample {private static class SharedResource {final Lock lock = new ReentrantLock();}private static class MyThread extends Thread {private final SharedResource resource;public MyThread(String name, SharedResource resource) {super(name);this.resource = resource;}@Overridepublic void run() {try {if (resource.lock.tryLock(500, TimeUnit.MILLISECONDS)) {try {System.out.println(getName() + " acquired lock on " + resource);// 执行业务逻辑} finally {resource.lock.unlock();System.out.println(getName() + " released lock on " + resource);}} else {System.out.println(getName() + " couldn't acquire lock on " + resource + " within 500 milliseconds");
运行结果:
Thread-1 acquired lock on SharedResource@hash1
Thread-2 couldn't acquire lock on SharedResource@hash1 within 500 milliseconds
Thread-1 released lock on SharedResource@hash1
Thread-1
首先获取了锁,而 Thread-2
在500毫秒内无法获取到锁,因此放弃锁的获取。
之后Thread-1
释放了锁。
lockInterruptibly()
此方法的作用是获取锁,如果这个锁可以获得,那么该方法就会立刻返回。如果获取不到 被其他线程占用了,就会一直等待。
使用为代码:
public void lockInterruptibly() { try {
lock.lockInterruptibly();
try { System.out.println(“操作资源”); } finally {
lock.unlock();
}
} catch (InterruptedException e) { e.printStackTrace();
}
}
unlock()
就是解锁。
对于ReentrantLock执行此方法,内部就会把锁,持有锁的计数器减去1,当减去1为0时 候。此锁就被释放了。
总结
- 引入
Lock
接口:Lock
接口提供了比传统的synchronized
关键字更为灵活和可扩展的锁定机制。在java.util.concurrent.locks
包中定义了多个实现了Lock
接口的类,其中最常用的是ReentrantLock
。 - 获取锁: 使用
lock()
方法来获取锁。这一步类似于使用synchronized
关键字进行同步,但提供了更灵活的控制。
lock.lock();
try {// 执行需要同步的代码块
} finally {lock.unlock();
}
- 释放锁: 在
finally
块中使用unlock()
方法释放锁,确保锁的释放,避免死锁和其他并发问题。 - 可中断性: 使用
lockInterruptibly()
方法可实现可中断的锁获取。在等待锁的过程中,线程可以被中断,以避免长时间的阻塞。
try {lock.lockInterruptibly(); // 可中断地获取锁// 执行需要同步的代码块
} catch (InterruptedException e) {// 处理中断异常
} finally {lock.unlock();
}
- 超时获取锁: 使用
tryLock(long time, TimeUnit unit)
方法,可以尝试在指定时间内获取锁。如果在指定时间内无法获取到锁,线程可以选择放弃或进行其他处理。
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {try {// 执行需要同步的代码块} finally {lock.unlock();}
} else {// 未能在指定时间内获取到锁
}
总的来说,Lock
提供了更多的控制和灵活性,适用于更复杂的并发场景。在使用时,需要注意合理释放锁,避免死锁,并根据具体需求选择合适的锁实现和锁策略。