Lock接口
# 一、Lock
Lock实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
2
3
4
5
6
7
在Lock中声明了四个方法来获取锁:lock()
、tryLock()
、tryLock(long time, TimeUnit unit)
和lockInterruptibly()
lock()
就是最普通的获取锁,如果锁已被其他线程获取,则等待;Lock
不会像synchronized
一样在异常时自动释放锁,因此我们需要手动释放锁,最佳实践:在finally中释放锁,以保证发生异常时锁一定被释放。此外lock()
方法不能被中断,这会有很大隐患,一旦陷入死锁,lock()
就会陷入永久等待。tryLock()
用来尝试获取锁,如果当前所没有被其他线程占用,则获取成功返回true,锁获取失败返回false;相比于lock()
,这样的方法显然功能更强大了,我们可以根据是否能获取到锁来决定后续的程序行为;且此方法会立即返回;tryLock(long time, TimeUnit unit)
和tryLock()
使用类似,不过它本身可以阻塞等待一段时间锁,超时过后再放弃。
用tryLock解决死锁
/**
* 使用tryLock来避免死锁
*
*/
public class DeadlockTryLock {
private static Lock lock1 = new ReentrantLock();
private static Lock lock2 = new ReentrantLock();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " got lock 1");
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " got lock1 and lock2 successfully.");
lock2.unlock();
lock1.unlock();
break;
} else {
System.out.println(Thread.currentThread().getName() + " fail to get lock2");
lock1.unlock();
}
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));
} else {
System.out.println(Thread.currentThread().getName() + " fail to get lock1");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " got lock 2");
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " got lock2 and lock1 successfully.");
lock1.unlock();
lock2.unlock();
break;
} else {
System.out.println(Thread.currentThread().getName() + " fail to get lock1");
lock2.unlock();
}
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));
} else {
System.out.println(Thread.currentThread().getName() + " fail to get lock2");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
Thread-0 got lock 1
Thread-1 got lock 2
Thread-0 fail to get lock2
Thread-1 got lock2 and lock1 successfully.
Thread-0 got lock 1
Thread-0 got lock1 and lock2 successfully.
2
3
4
5
6
lockInterruptibly()
相当于tryLock(long time, TimeUnit unit)
把超时时间设置为无线。并且在等待锁的过程中,线程可以被中断。
/**
* @author yiren
*/
public class LockInterruptibly {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
try {
System.out.println(Thread.currentThread().getName() + " try to get lock");
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + " got lock");
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " sleep ");
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " unlock");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " lockInterruptibly ");
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
Thread.sleep(2000);
thread2.interrupt();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Thread-1 try to get lock
Thread-0 try to get lock
Thread-1 got lock
Thread-1 sleep
Thread-1 unlock
Thread-0 got lock
Thread-0 unlock
2
3
4
5
6
7
# 1、ReentrantLock可重入锁
ReentrantLock叫做可重入锁,所谓可重入锁,顾名思义,指的是线程可以重复获取同一把锁。
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 2、ReentrantLock与synchronized的区别
1、重入 synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
2、实现 synchronized是JVM实现的、而ReentrantLock是JDK实现的。说白了就是,是操作系统来实现,还是用户自己敲代码实现。
3、性能 在 Java 的 1.5 版本中,synchronized 性能不如 SDK 里面的 Lock,但 1.6 版本之后,synchronized 做了很多优化,将性能追了上来。
4、功能 ReentrantLock锁的细粒度和灵活度,都明显优于synchronized ,毕竟越麻烦使用的东西肯定功能越多啦!
# 二、锁的分类
https://juejin.im/post/6844904073993388039
https://developer.aliyun.com/article/759945
class Ticket {
private int number = 30;
ReentrantLock lock = new ReentrantLock();
public void buyTicket() {
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "\t卖出" + number-- + "\t还剩下" + number + "张票。");
}
} finally {
lock.unlock();
}
}
}
public class BuyTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.buyTicket();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.buyTicket();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.buyTicket();
}
}, "C").start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38