锁(八)

在java多线程中,我们知道使用synchronized关键字来实现线程间的同步互斥操作,那么其实还有一个更优秀的机制去完成“同步互斥”工作,它就是Lock对象,即重入锁和读写锁。他们具有比synchronized更为强大的功能,并且具有嗅探锁定、多路分支等功能。

ReentrantLock(重入锁)

  • 概念:
    在需要进行同步的代码部分加上锁定,但最后一定要释放锁,不然会造成锁用于无法释放,其他线程永远进不来的结果。
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
public class UseReentrantLock {

final Lock lock = new ReentrantLock();

public void method1(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入method1...");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "退出method1...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}

}

public void method2(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入method2...");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "退出method2...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}

public static void main(String[] args) {
final UseReentrantLock url = new UseReentrantLock();

Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
url.method1();
url.method2();
}
},"t1");

t1.start();
}

}

/*
打印结果:
t1进入method1...
t1退出method1...
t1进入method2...
t1退出method2...
*/

锁与等待/通知

我们在使用synchronized的时候,如果需要多线程间进行协作工作则需要Object的wait()和notify()、notifyAll方法配合使用。同样,我们在使用Lock的时候,可以使用一个新的等待/通知的类Condition,这个Condition一定是针对具体的某一把锁,也就是在只有锁的基础之上才会产生Condition。

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
public class UseCondition {

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

public void method1(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入等待状态...method1...");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "释放锁...");
condition.await(); //await 释放锁
System.out.println(Thread.currentThread().getName() + "继续执行...method1...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}

}

public void method2(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入method2...");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "发出唤醒...");
condition.signal(); //notify 不释放锁
System.out.println(Thread.currentThread().getName() + "不释放锁...");
System.out.println(Thread.currentThread().getName() + "执行完毕...method2...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}

public static void main(String[] args) throws InterruptedException {
final UseCondition uc = new UseCondition();

Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
uc.method1();
}
},"t1");

Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
uc.method2();
}
},"t2");

t1.start();
Thread.sleep(1000);
t2.start();

}
}
/*
打印结果:
t1释放锁...
t2进入method2...
t2发出唤醒...
t2不释放锁...
t2执行完毕...method2...
t1继续执行...method1...
*/

多个Condition

可以通过一个Lock对象产生多个Condition进行线程间的交互,非常的灵活。可以使得部分需要唤醒的线程唤醒,其他的线程则继续等待通知。

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
public class UseManyCondition {

private Lock lock = new ReentrantLock();

private Condition c1 = lock.newCondition();

private Condition c2 = lock.newCondition();

public void method1(){

try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入method1...");
Thread.sleep(1000);
c1.await();
System.out.println(Thread.currentThread().getName() + "退出method1...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}

public void method2(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入method2...");
Thread.sleep(1000);
c1.await();
System.out.println(Thread.currentThread().getName() + "退出method2...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}

public void method3(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入method3...");
Thread.sleep(1000);
c2.await();
System.out.println(Thread.currentThread().getName() + "退出method3...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}

public void method4(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入method4...");
c1.signalAll();
System.out.println(Thread.currentThread().getName() + "c1释放锁...");
} catch (Exception e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}

public void method5(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入method5...");
c2.signal();
System.out.println(Thread.currentThread().getName() + "c2释放锁...");
} catch (Exception e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}

public static void main(String[] args) throws InterruptedException {
final UseManyCondition umc = new UseManyCondition();

Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
umc.method1();
}},"t1");

Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
umc.method2();
}},"t2");

Thread t3 = new Thread(new Runnable(){
@Override
public void run() {
umc.method3();
}},"t3");

Thread t4 = new Thread(new Runnable(){
@Override
public void run() {
umc.method4();
}},"t4");

Thread t5 = new Thread(new Runnable(){
@Override
public void run() {
umc.method5();
}},"t5");

t1.start();
t2.start();
t3.start();

Thread.sleep(1000);

t4.start();
t5.start();
}

}
/*
打印结果:
t1进入method1...
t3进入method3...
t2进入method2...
t4进入method4...
t4c1释放锁...
t5进入method5...
t5c2释放锁...
t1退出method1...
t2退出method2...
t3退出method3...
*/

Lock/Condition其他用法

公平锁和非公平锁:ReentrantLock lock = new ReentrantLock(boolean isFair),lock用法:

  • lock.tryLock():
    尝试获得锁,获得结果用true/false返回。
  • lock.tryLock(paramLong, paramTimeUnit):
    在给定的时间内尝试获得锁,获得结果用true/false返回。
  • lock.isFair():
    是否公平锁。
  • lock.isLocked():
    是否锁定。
  • lock.getHoldCount():
    查询当前线程保持此锁的个数,也就是调用lock()次数。
  • lock.lockInterruptibly():
    优先响应中断的锁。
  • lock.getQueueLength():
    返回正在等待获取此锁的线程数。
  • lock.getWaitQueueLength(Condition paramCondition):
    返回等待与锁定相关的给定条件Condition的线程数。
  • lock.hasQueuedThread(Thread paramThread):
    查询指定的线程是否正在等待此锁。
  • lock.hasQueuedThreads():
    查询是否有线程正在等待此锁。
  • lock.hasWaiters(Condition paramCondition):
    查询是否有线程正在等待与此锁定有关的condition条件。

ReentrantReadWriteLock(读写锁)

读写锁,其核心就是实现读写分离。在高并发访问下,尤其是读多写少的情况下,性能要员高于重入锁。对于synchronized、ReentrantLock,我们知道,同一时间内,只能有一个线程进行访问被锁定的代码。读写锁则不同,其本质是分成两个锁,即读锁和写锁。在读锁下,多个线程可以并发的进行访问,但是在写锁的时候,只能一个一个顺序访问,即读读共享,写写互斥,读写互斥。

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
public class UseReentrantReadWriteLock {

private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private ReadLock readLock = lock.readLock();
private WriteLock writeLock = lock.writeLock();

public void read(){
try {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "进入read...");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "退出read...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
readLock.unlock();
}
}

public void write(){
try {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "进入write...");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "退出write...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
writeLock.unlock();
}
}

public static void main(String[] args) {
final UseReentrantReadWriteLock urrwl = new UseReentrantReadWriteLock();

Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
urrwl.read();
}},"t1");

Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
urrwl.read();
}},"t2");

Thread t3 = new Thread(new Runnable(){
@Override
public void run() {
urrwl.write();
}},"t3");

Thread t4 = new Thread(new Runnable(){
@Override
public void run() {
urrwl.write();
}},"t4");

//读读共享
t1.start();
t2.start();

//读写互斥
t1.start();
t3.start();

//写写互斥
t3.start();
t4.start();
}

}
/*
打印结果:
t1进入read...
t2进入read...
t2退出read...
t1退出read...
t3进入write...
t3退出write...
* */

锁优化

  1. 避免死锁。
  2. 减小锁持有时间。
  3. 减小锁的粒度。
  4. 锁的分离。
  5. 尽量使用无锁的操作,如原子操作(Atomic系列类),volatile关键字。
-------------本文结束感谢您的阅读-------------