JUC高并发-Lock接口概述与实现

  1. 1. 一、Synchronized 和 Lock的概念
  2. 2. 二、Lock 和 synchronize 有以下不同点:
  3. 3. 三、代码实现:
  4. 4. 四、 多线程编程步骤
  5. 5. 五、Lock实现 - 线程间通信

Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。

一、Synchronized 和 Lock的概念

​ Synchronized 是 Java 并发编程中很重要的关键字,另外一个很重要的是 volatile。Syncronized 的目的是一次只允许一个线程进入由他修饰的代码段,从而允许他们进行自我保护。Synchronized 很像生活中的锁例子,进入由Synchronized 保护的代码区首先需要获取 Synchronized 这把锁,其他线程想要执行必须进行等待。Synchronized 锁住的代码区域执行完成后需要把锁归还,也就是释放锁,这样才能够让其他线程使用。

​ Lock 是 Java并发编程中很重要的一个接口,它要比 Synchronized 关键字更能直译”锁”的概念,Lock需要手动加锁和手动解锁,一般通过 lock.lock() 方法来进行加锁, 通过 lock.unlock() 方法进行解锁。与 Lock 关联密切的锁有 ReetrantLock 和 ReadWriteLock。

​ ReetrantLock 实现了Lock接口,它是一个可重入锁,内部定义了公平锁与非公平锁。

​ ReadWriteLock 一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。ReentrantReadWirteLock实现了ReadWirteLock接口,并未实现Lock接口。

二、Lock 和 synchronize 有以下不同点:

  • 存在层面:Syncronized 是Java 中的一个关键字,存在于 JVM 层面,Lock 是 Java 中的一个接口
  • 锁的释放条件:1. 获取锁的线程执行完同步代码后,自动释放;2. 线程发生异常时,JVM会让线程释放锁;Lock 必须在 finally 关键字中释放锁,不然容易造成线程死锁
  • 锁的获取: 在 Syncronized 中,假设线程 A 获得锁,B 线程等待。如果 A 发生阻塞,那么 B 会一直等待。在 Lock 中,会分情况而定,Lock 中有尝试获取锁的方法,如果尝试获取到锁,则不用一直等待,可进行中断锁
  • 锁的状态:Synchronized 无法判断锁的状态,Lock 则可以判断
  • 锁的类型:Synchronized 是可重入,不可中断,非公平锁;Lock 锁则是可重入,可判断,可公平锁
  • 锁的性能:Synchronized 适用于少量同步的情况下,性能开销比较大。Lock 锁适用于大量同步阶段:
  • Lock 锁可以提高多个线程进行读的效率(使用 readWriteLock)
  • 在竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
  • ReetrantLock 提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。

三、代码实现:

1
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
   // 第一步: 创建资源类,定义属性和操作方法
class LTicket {
// 票数量
private int number = 30;

// 创建可重入锁
private final ReentrantLock lock = new ReentrantLock();

// 卖票方法
public void sale() {
// 上锁
lock.lock();;
try {
// 判断是否有锁
if (number>0) {
System.out.println(Thread.currentThread().getName()+": 卖出"+number-- + "剩余:"+number);
}
} finally {
// 解锁
lock.unlock();
}

}
}

public class LSaleTicket {
// 第二步: 创建多个线程,调用资源类的方法
// 创建三个线程
public static void main(String[] args) {
LTicket ticket = new LTicket();

new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "AA").start();

new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "BB").start();

new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "CC").start();

}

}

测试结果:

四、 多线程编程步骤

第一步 创建资源类,在资源类创建属性和操作方法。

第二步 在资源类操作方法中(1.判断 2.干活 3.通知)。

第三步 创建多个线程,调用资源类的操作方法。

第四步 防止虚假唤醒问题。

五、Lock实现 - 线程间通信

例子: 有两个线程,实现对一个初始值是0的变量,一个线程对值+1,另外一个线程对值-1.

代码实现:

1
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// 第一步创建资源类
class Share {
private int number = 0;
// 创建 Lock
private Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

// +1操作方法
public void incr() throws InterruptedException {
// 上锁
lock.lock();
try {
// 判断
while (number != 0) {
condition.await();
}
// 如果当前值为0,则加1. 干活
number++;
System.out.println(Thread.currentThread().getName() + " ::" + number);
// 通知
condition.signalAll();
} finally {
// 解锁
lock.unlock();
}
}


// -1
public void decr() throws InterruptedException {
lock.lock();
try {
while (number != 1) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + " ::" + number);
condition.signalAll();
} finally {
lock.unlock();
}

}
}


public class ThreadDemo2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();

new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();

new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();

new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}

运行结果: