多线程经典实例(一)

在多线程的学习当中,有许多经典的例子值得学习,比如售票窗口、线程交替执行、生产者消费者等。下面就来看两个有关线程交替执行的案例。

两个线程轮流打印数字

这里采用 wait/notify 等待通知和 Lock/Condition 两种方式实现。

wait()/notify()实现

简单介绍一下wait/notify机制的几个方法。
以下4个方法都必须在获取了锁的情况下才能调用:

wait(): 使当前线程进入阻塞等待状态,直到被唤醒或中断; 调用后立即释放已有的锁;
wait(Long times): 使当前线程进入阻塞等待状态一段时间,超过时间后自动唤醒;
notify(): 唤醒在该对象上等待的一个线程;
notifyAll(): 唤醒在该对象上等待的所有线程。

释放锁的场景主要有3种:

  1. 执行完同步方法/代码块
  2. 执行同步方法/代码块的过程中遇到异常
  3. 执行同步方法/代码块的过程中调用了锁对象的wait()方法
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
public class TurnsPrintNumber {
private static Object lock = new Object(); //锁
private static int i = 1;
static class Print implements Runnable{
@Override
public void run() {
while (true) {
synchronized (lock) {
if ( i > 20) {
System.out.println("打印完毕!");
lock.notify();
return;
}
System.out.println("线程" + Thread.currentThread().getName() + "打印:" + i ++ );
lock.notify();
try {
Thread.sleep(100);
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
Print print = new Print();
new Thread(print).start();
new Thread(print).start();
}
}
Lock/Condition实现

对于同一个锁(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
public class TurnsPrintNumber2 {
private static ReentrantLock lock = new ReentrantLock();
private Condition a = lock.newCondition();
private Condition b = lock.newCondition();
static int i = 1;
private void print () {
new Thread(() ->{
while (i < 20) {
lock.lock();
System.out.println("线程1打印: " + i++);
try {
a.await(); //将线程1从运行状态->阻塞等待
b.signal();//唤醒线程2
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}).start();
new Thread(() ->{
while (i < 20) {
lock.lock();
System.out.println("线程2打印: " + i++);
try {
a.signal();
b.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}).start();
}
public static void main(String[] args) {
new TurnsPrintNumber2().print();
}
}

三个线程轮流打印 ABC

线程1 打印 A,线程2 打印 B,线程3 打印 C, 线程1 打印 A………..

采用以下3种方式实现:

  1. wait()/notify()方式
  2. Lock/Condition方式
  3. Semaphore信号量方式
思路:

要在3个线程间实现轮流打印的操作,最重要的就是要控制3个线程的执行顺序。也就是要确定等待、唤醒的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。

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
public class TurnsPrintABCWaitNotify {
private static Object alock = new Object();
private static Object block = new Object();
private static Object clock = new Object();
public static void main(String[] args) {
new Thread(() ->{
for (int i = 0; i < 5; i++) {
synchronized (alock) { //先获取 A 的锁
synchronized (block) { //再获取 B 的锁
System.out.println("线程1打印:A");
block.notify(); //唤醒 B
}
if (i == 5){return;}//打印5次后结束
try {
alock.wait(); //释放 A 的锁,进入等待阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(() ->{
for (int i = 0; i < 5; i++) {
synchronized (block) {
synchronized (clock) {
System.out.println("线程2打印:B");
clock.notifyAll();
}
try {
block.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(() ->{
for (int i = 0; i < 5; i++) {
synchronized (clock) {
synchronized (alock) {
System.out.println("线程3打印:C");
alock.notifyAll();
}
try {
clock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
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
public class TurnsPrintABCLock {
private static ReentrantLock lock = new ReentrantLock();
private Condition a = lock.newCondition();
private Condition b = lock.newCondition();
private Condition c = lock.newCondition();
int i = 1;
private void print () {
new Thread(() ->{
while (i < 10) {
lock.lock();
System.out.println("线程1打印: " + "A");
i++;
if (i >= 10) {
System.out.println("打印完毕!");
return;
}
try {
b.signal(); //唤醒线程2
a.await(); //将线程1从运行状态->阻塞等待
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() ->{
while (i < 10) {
lock.lock();
System.out.println("线程2打印: " + "B");
i++;
if (i >= 10) {
System.out.println("打印完毕!");
return;
}
try {
c.signal();
b.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() ->{
while (i < 10) {
lock.lock();
System.out.println("线程3打印: " + "C");
i++;
if (i >= 10) {
System.out.println("打印完毕!");
return;
}
try {
a.signal();
c.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}).start();
}
public static void main(String[] args) {
new TurnsPrintABCLock().print();
}
}
Semaphore信号量实现
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
public class TurnsPrintABCSemaphore {
// A初始信号量数量为1
private static Semaphore A = new Semaphore(1);
// B、C初始信号数量为0
private static Semaphore B = new Semaphore(0);
private static Semaphore C = new Semaphore(0);
static class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
A.acquire();// A获取信号执行,A信号量减1,当A为0时将无法继续获得该信号量
System.out.println("线程1打印: " + "A");
B.release();
//System.out.println(B.drainPermits());//B释放之后信号量加1(初始为0),可以查看到 B 的信号量为1
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class ThreadB extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
B.acquire();
System.out.println("线程2打印: " + "B");
C.release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class ThreadC extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
C.acquire();
System.out.println("线程3打印: " + "C");
A.release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
new ThreadA().start();
new ThreadB().start();
new ThreadC().start();
}
}

以上就是有关多个线程交替执行的例子。

扩展

如何保证线程的顺序执行?比如有a,b,c三个线程,如何保证a执行完再执行b,b执行完再执行c?

  1. Join
  2. 线程池(newSingleThreadExecutor)
  3. CountDownLatch/Semaphore