Java多线程核心技术阅读笔记(第四章)

Java多线程 Lock的使用

Posted by 刘知安 on 2019-11-09
文章目录
  1. 第4章 Lock的使用
  2. 4.1 重入锁
    1. 4.1.1 重入锁实现同步
    2. 4.1.2 重入锁实现wait/notify
    3. 4.1.3 重入锁实现 消费者/生产者问题

第4章 Lock的使用

4.1 重入锁

所谓重入锁,就是可以重新进入的锁。。废话!反正我用纯文字解释不了他是什么意思。还是举个例子解释它的概念吧。
假如有n个线程,都想操作共享变量obj,现在假设线程A的method1()通过lock.lock()拿到了锁,在释放锁之前method1()又调用了
method2()method2()开头第一句就是lock.lock()尝试去获得锁。在这种情况下,method1()调用method2()使得method2()中代码
得以成功执行。也就是Re-Entrance的意思,即所谓重入锁。

BTW,重入锁默认是非公平锁。

重入锁有三个获得相关线程数目的方法:

  • 重入锁底层采用的是计数的方式,已经获得锁的线程每调用一次lock(),计数器+1,相反每调用一次unlock(),计数器-1。可以通过lock.getHoldCount()获得当前计数器的值。

  • 上述针对的都是已经获得锁的线程而言的。我们知道,Thread.sleep()函数会让当前线程等待,且与wait()最大的区别就是不会释放锁。现在假设有n个线程,其中有一个拿到了锁,但是中间因为某些原因,被无限地睡眠了。
    这个时候,其他n-1个线程又在等这个锁释放,可以通过lock.getQueueLength()获得当前正在等待锁的线程数。

  • lock.getWaitQueueLength()lock.getQueueLength()很相似,区别是前者返回的是等待与此锁相关的给定Condition对象的线程数。如果有5个线程,每个线程都调用了Condition对象cond的await()方法,则返回5.

4.1.1 重入锁实现同步

Java中出了用synchronized关键字实现同步,也可以用ReentrantLock对象来实现。
可以把synchronized同步的那个对象看做是一个重入锁。

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 ch04;

import java.util.concurrent.locks.ReentrantLock;

/**
* @ClassName ReentrantLockTest
* @Deacription // TODO
* @Author LiuZhian
* @Date 2019-11-09 15:52
* @Version 1.0
**/

public class ReentrantLockTest {

public static void main(String[] args) {
MyService sv = new MyService();
MyThread[] ths = new MyThread[5];
for (int i = 0; i < 5; i++) {
ths[i] = new MyThread(sv);
ths[i].setName("" + i);
ths[i].start();
}
}
}

class MyService {
private ReentrantLock lock;

public MyService() {
this.lock = new ReentrantLock();
}

public void testMethod() {
lock.lock();
for (int i = 0; i < 3; i++)
System.out.println((i + 1) + "--> Thread name is " + Thread.currentThread().getName());
lock.unlock();
}
}

class MyThread extends Thread {
private MyService service;

public MyThread(MyService service) {
this.service = service;
}

@Override
public void run() {
super.run();
service.testMethod();
}
}

4.1.2 重入锁实现wait/notify

在等待/通知机制中,我们通过调用obj.wait()方法,使得当前线程(这里的当前线程指的是当前占用了用CPU的线程)等待obj完成。
之前在第三章说过,在调用wait()、notify()、notifyAll()方法前必须得到对象锁,也就是说,要判断共享变量obj是否到了不再等待的状态,我们必须先拿到这个共享变量obj的锁。
在锁的世界中,等待某个共享变量是否达到一个状态叫Condition。在Condition对象cond.await()cond.notify()之前,必须获得对象监视器,也就是我们说的锁。

扯了这么多,看个代码就知道了:

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
package ch04;

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

/**
* @ClassName ReentrantLockWaitNotifyTest
* @Deacription // TODO
* @Author LiuZhian
* @Date 2019-11-09 16:17
* @Version 1.0
**/

public class ReentrantLockWaitNotifyTest {
public static void main(String[] args) {
MyServ serv = new MyServ();
TestThreadA tA = new TestThreadA(serv);
tA.start();
// 这里main 线程等2秒,目的是防止在await之前signal
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
serv.signalMethod();
}
}

class MyServ {
private ReentrantLock lock = new ReentrantLock();
private Condition cond = lock.newCondition();

public void waitMethod() {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁,开始等待!");
try {
cond.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁!");
}
}

public void signalMethod()
{
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁,开始通知!");
cond.signal();
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁,通知完毕!");
}
}


class TestThreadA extends Thread {
private MyServ serv;

public TestThreadA(MyServ serv) {
this.serv = serv;
}

@Override
public void run() {
super.run();
serv.waitMethod();
}
}

4.1.3 重入锁实现 消费者/生产者问题

上面提到的Condition,其实并不是我们通常说的true/false这样的condition,而是同步互斥中的竞争条件,是一种抽象的概念,我更喜欢把它理解为控制进程等待状态的一个等待条件。
举生产者/消费者的例子来说,我们真的需要一个T/F条件来控制wait和notify。代码如下:

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
package ch04;

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

/**
* @ClassName ReentrantLockPC
* @Deacription // TODO
* @Author LiuZhian
* @Date 2019-11-09 16:43
* @Version 1.0
**/

public class ReentrantLockPCTest {
public static void main(String[] args) {
MyProducerConsumer mpc = new MyProducerConsumer();
MyP p = new MyP(mpc);
MyC c = new MyC(mpc);
p.start();
c.start();
}


}

class MyP extends Thread {
private MyProducerConsumer mpc;

public MyP(MyProducerConsumer mpc) {
this.mpc = mpc;
}

@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++)
mpc.produce();
}
}

class MyC extends Thread {
private MyProducerConsumer mpc;

public MyC(MyProducerConsumer mpc) {
this.mpc = mpc;
}

@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++)
mpc.consume();
}
}

class MyProducerConsumer {
ReentrantLock lock = new ReentrantLock();
Condition cond = lock.newCondition();
boolean hasItem = false;

public void produce() {
try {
lock.lock();
while (hasItem)
cond.await();
System.out.println("生产者生产了一个项目!");
hasItem = true;
cond.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void consume() {
try {
lock.lock();
while (!hasItem)
cond.await();
System.out.println("消费者消费了一个项目!");
hasItem = false;
cond.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}