前言:

本文内容:Lock锁、传统生产者消费者问题、防止虚假唤醒、Lock锁的生产者消费者问题

推荐免费JUC并发编程视频:【狂神说Java】JUC并发编程最新版通俗易懂_哔哩哔哩_bilibili

Lock锁

概述

从Java5开始,Java提供了一种功能更强大的线程同步机制——通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当

公平锁于非公平锁

公平锁:多个线程按照申请锁的顺序来获取锁

非公平锁(默认选择):多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象

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
package com.jokerdig;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* @author Joker大雄
* @data 2022/8/16 - 14:14
**/
// 基本售票例子
/*
真正的多线程开发
线程就是一个单独的资源类,没有任何负数操作
包含:属性、方法
*/
public class SaleTicketDemo02 {
public static void main(String[] args) {
// 并发,多线程操作同一个资源类
Ticket2 ticket = new Ticket2();
// @FunctionalInterface 函数表达式接口
// 使用lambda表达式(参数)->{代码}
new Thread(() -> { for (int i = 1; i < 40; i++) ticket.sale(); }, "A").start();
new Thread(() -> { for (int i = 1; i < 40; i++) ticket.sale(); }, "B").start();
new Thread(() -> { for (int i = 1; i < 40; i++) ticket.sale(); }, "C").start();
}
}

// Lock
/*
1. new ReentrantLock();
2. lock.lock(); 加锁
3. lock.unlock(); 解锁
*/
class Ticket2{
// 属性 方法
private int number = 30;
// 卖票 使用Lock
Lock lock = new ReentrantLock();

public void sale(){
lock.lock();// 加锁

try {
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock(); // 解锁
}
}
}

运行结果

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
A卖出了第30票,剩余:29
A卖出了第29票,剩余:28
A卖出了第28票,剩余:27
A卖出了第27票,剩余:26
A卖出了第26票,剩余:25
A卖出了第25票,剩余:24
C卖出了第24票,剩余:23
C卖出了第23票,剩余:22
C卖出了第22票,剩余:21
C卖出了第21票,剩余:20
C卖出了第20票,剩余:19
C卖出了第19票,剩余:18
C卖出了第18票,剩余:17
C卖出了第17票,剩余:16
C卖出了第16票,剩余:15
C卖出了第15票,剩余:14
C卖出了第14票,剩余:13
C卖出了第13票,剩余:12
C卖出了第12票,剩余:11
C卖出了第11票,剩余:10
C卖出了第10票,剩余:9
C卖出了第9票,剩余:8
C卖出了第8票,剩余:7
C卖出了第7票,剩余:6
C卖出了第6票,剩余:5
C卖出了第5票,剩余:4
C卖出了第4票,剩余:3
C卖出了第3票,剩余:2
C卖出了第2票,剩余:1
C卖出了第1票,剩余:0

Process finished with exit code 0

Synchronized和Lock的区别

  1. Synchronized是内置的关键字,Lock是一个Java类
  2. Lock允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象
  3. synchronized无法判断锁的状态,Lock可以判断是否获取到了锁
  4. synchronized会自动释放锁,lock必须要手动释放,不释放会产生死锁
  5. synchronized线程1(获得锁,假如获取阻塞),线程2(等待,会一直等待);Lock锁就不一定会等待
  6. synchronized是可重入锁,不可以中断,非公平;Lock锁是可重入锁,可以判断锁,非公平(可以自定义)
  7. synchronized适合锁少量同步代码,Lock适合锁大量的同步代码

传统生产者消费者问题、防止虚假唤醒

面试常考:单例模式、排序算法、生产者和消费者、死锁

传统生产者消费者问题

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
package com.jokerdig.pc;

/**
* @author Joker大雄
* @data 2022/8/17 - 10:22
**/
/*
线程之间通信问题:生产者和消费者问题
线程交替执行 A B 操作同一个变量 num=0
等待唤醒 通知唤醒
A num+1
B num-1
*/
public class A {
public static void main(String[] args) {
Data data = new Data();

new Thread(()->{
for(int i = 0;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();

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

}
// 判断等待 业务 通知
class Data{ // 数字 资源类
private int num = 0;

// +1
public synchronized void increment() throws InterruptedException {
if(num!=0){
// 等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"->"+num);
// 通知其他线程,+1完毕
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
if(num==0){
// 等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"->"+num);
// 通知其他线程,-1完毕
this.notifyAll();
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0

Process finished with exit code 0

防止虚假唤醒

问题:假设存在ABCD四个线程,运行会出现问题(我们使用了if)

线程也可以唤醒,而不会被通知,中断或超时,即虚假唤醒。虽然实际开发中很少发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。等待应该总是出现在循环中

解决办法将if判断改为while即可

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
package com.jokerdig.pc;

/**
* @author Joker大雄
* @data 2022/8/17 - 10:22
**/
/*
线程之间通信问题:生产者和消费者问题
线程交替执行 A B 操作同一个变量 num=0
等待唤醒 通知唤醒
A num+1
B num-1
*/
public class A {
public static void main(String[] args) {
Data data = new Data();

new Thread(()->{
for(int i = 0;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();

new Thread(()->{
for(int i = 0;i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for(int i = 0;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();

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

}
// 判断等待 业务 通知
class Data{ // 数字 资源类
private int num = 0;

// +1
public synchronized void increment() throws InterruptedException {
// 使用while防止虚假唤醒
while(num!=0){
// 等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"->"+num);
// 通知其他线程,+1完毕
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
// 使用while防止虚假唤醒
while(num==0){
// 等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"->"+num);
// 通知其他线程,-1完毕
this.notifyAll();
}
}

Lock锁的生产者消费者问题

22

代码实现

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package com.jokerdig.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* @author Joker大雄
* @data 2022/8/17 - 10:22
**/
/*
线程之间通信问题:生产者和消费者问题
线程交替执行 A B 操作同一个变量 num=0
等待唤醒 通知唤醒
A num+1
B num-1
*/
public class B {
public static void main(String[] args) {
Data1 data = new Data1();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();

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

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

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

// 判断等待 业务 通知
class Data1{ // 数字 资源类
private int num = 0;
// Lock锁
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// +1
public void increment() throws InterruptedException {
try {
lock.lock();
// 使用while防止虚假唤醒
while(num!=0){
// 等待
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"->"+num);
// 通知其他线程,+1完毕
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// -1
public void decrement() throws InterruptedException {
try {
lock.lock();
// 使用while防止虚假唤醒
while(num==0){
// 等待
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"->"+num);
// 通知其他线程,-1完毕
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

存在问题:现在执行都是随机的

需求:按照顺序执行,精准的通知和唤醒