为了解决线程同步问题我们经常使用synchronized关键字和Lock接口下的锁来解决同步问题。但是这两者有什么区别之处呢?首先我们分别使用synchronized和Lock来解决生产者消费者问题。

生产者消费者问题

当生产者生产数量为1的时候,通知消费者消费。当生产者产品数量为0的时候,生产产品。

synchronized

image-20200924113539638

image-20200924101556523

运行测试,结果符合预期!

image-20200924110342093

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
/**
* 线程之间的通信问题:生产者和消费者问题!等待唤醒,通知唤醒
* 线程交替执行 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 number = 0;


//+1
public synchronized void increment() throws InterruptedException {
while (number != 0) {
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我+1完成了
this.notifyAll();
}

//-1
public synchronized void decrement() throws InterruptedException {
while (number == 0) {
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我-1完成了
this.notifyAll();
}

}

Lock

要想使用Lock达到线程阻塞唤醒,我们必须要使用到Condition

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作。

Condition是个接口,基本的方法就是await()和signal()方法。

Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()。调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

  • Conditon中的await()对应Object的wait();

  • Condition中的signal()对应Object的notify();

  • Condition中的signalAll()对应Object的notifyAll()

image-20200924160336344

image-20200924160523225

image-20200924160604985

image-20200924160801058

image-20200924160919309

image-20200924161026913

运行查看结果,符合预期!

image-20200924161108459

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
/**
* 线程之间的通信问题:生产者和消费者问题!等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class B {

public static void main(String[] args) {
Data2 data = new Data2();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.decrement();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.increment();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.decrement();
}
}, "D").start();
}
}

//判断等待, 业务,通知
class Data2 {

private int number = 0;

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

//+1
public void increment() {
//上锁
lock.lock();
try {
while (number != 0) {
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我+1完成了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}

}

//-1
public void decrement() {
//上锁
lock.lock();

try {
while (number == 0) {
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我-1完成了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
}

}

两者对应关系

image-20200924161717349

synchroized和Lock的区别

原始构成

synchronized是java关键字属于JVM层面,底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象。只有在同步代码块中或方法中才能调用wait/notify等方法。

Lock是具体类(java.util.concurrent.locks.Lock)是api层面的锁。

获取锁的状态

synchornized无法判断是否获取锁的状态

Lock可以通过·tryLock()判断是否获取到锁。

image-20200924162528217

image-20200924162619935

使用方法

synchronized不需要用户去手动释放锁,当synchronized代码执行完成后系统会自动让线程释放对锁的占用。

Lock则需要用户去手动释放锁,否则可能出现没有主动释放锁而导致的死锁现象。所以lock()unlock()方法配合try/finally语句块来完成。

异常是否释放锁

synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁

lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)

等待是否可中断

synchronized不可中断,除非抛出异常或者正常运行完成。

Lock可中断

  1. 通过设置超时方法中断tryLock(long timeout,TimeUnit unit)
  2. 通过lockInterruptibly()

image-20200924164532724

image-20200924164651414

image-20200924164710271

加锁是否公平

synchronized非公平锁

ReentrantLock两者都可以,默认非公平锁,构造方法传入boolean值,true为公平锁,false为非公平锁。

image-20200924165202348

是否一直等待

使用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去。(不可中断)

Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束啦。(可以中断)

能否精确唤醒线程

synchronized不能,只能随机唤醒一个(notify)或者唤醒全部等待线程(notifyall)

Lock可以使用condition达到精确唤醒

性能问题

在性能上来说,如果竞争资源不够激烈,两者的性能是差不多的,而当竞争资源非常激烈是(有大量线程同时竞争),此时Lock的性能远远优于synchronzed。

synchronized适合锁代码少量的同步问题

Lock锁适合大量同步的代码的同步问题。

精准唤醒

使用Lock加Condition精准唤醒,完成A执行完调用B,B执行完调用C,C执行完调用A。

image-20200924170443331

image-20200924170701028

image-20200924170800991

image-20200924170813490

运行测试。查看结果

image-20200924170850765

image-20200924172146372

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
/**
* A执行完调用B,B执行完调用C,C执行完调用A。
*/
public class C {

public static void main(String[] args) {

Data3 data = new Data3();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printC();
}
}, "C").start();
}
}

class Data3 {

private Lock lock = new ReentrantLock();

//1A 2B 3C
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();

private int number = 1;

public void printA() {
lock.lock();

try {
while (number != 1) {
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAAAAAAA");
number = 2;
//唤醒,唤醒指定的B
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}

}

public void printB() {
lock.lock();

try {
while (number != 2) {
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBBBBBBB");
number = 3;
//唤醒,唤醒指定的C
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void printC() {
lock.lock();

try {
while (number != 3) {
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "=>CCCCCCCC");
number = 1;
//唤醒,唤醒指定的A
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

}