Java多线程实现交替运行

这是我去年阿里笔试的时候遇到的题,要求三个线程交替打印 ‘a,l,i’(当时根本没来得及做),这次翻出来看看原来好简单(现在看那时候的五题都挺简单的,还是那时候准备的太少了,活该被刷)(现在也准备的少…)

关于线程的站内链接:Java多线程-1-线程安全与锁Java多线程-2-可重入锁以及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
public class Work2 {
static final Object object = new Object();

public static void main(String[] args) {

Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
for (int i = 1; i < 10; i += 2) {
System.out.println(Thread.currentThread().getName() + " " + i);
object.notify();
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
for (int i = 2; i < 10; i += 2) {
System.out.println(Thread.currentThread().getName() + " " + i);
object.notify();
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "t2");
t1.start();
t2.start();
}
}

运行结果:

1
2
3
4
5
6
7
8
9
t1    1
t2 2
t1 3
t2 4
t1 5
t2 6
t1 7
t2 8
t1 9

该实现的原理也非常简单,定义一个对象,因为一个对象有且只有一个锁,所以让两个线程循环让此对象解锁->加锁,从而达到线程交替运行的目的。

创建三个线程交替循环打印’A,B,C’

法一:synchronized

主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就是前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印字母,之后首先调用self.notifyAll()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A……

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
public class ABC {
static class ThreadPrinter implements Runnable {

static int i = 0;

private String name;// 打印的内容
private Object prev;// 上一个线程持有的锁
private Object self;// 自己持有的锁

// 构造方法
public ThreadPrinter(String name, Object prev, Object self) {
super();
this.name = name;
this.prev = prev;
this.self = self;
}

@Override
public void run() {
// 首先获取上一个线程的锁
// 然后再获取自己的锁
// 持有两把锁才能打印A|B|C
// 打印完了之后首先释放自己的所标记 self.notifyAll();
// 然后释放上一个线程的所标记 prev.wait();
// 多线程并发,不能用if,必须用循环测试等待条件,避免虚假唤醒
while (i < 30) {
// 先获取prev锁
synchronized (prev) {
// 再获取self锁
synchronized (self) {
System.out.print(name);
i++;
self.notifyAll();// 先释放 self,唤醒其他线程竞争self锁
}
/**
* 注意的是notify()调用后,并不是马上就释放对象锁,而是在相应的synchronized(){}语句块执行结束,自动释放锁,
* JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。
*/
try {
// 再释放 prev,休眠等待唤醒
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

public static void main(String[] args) throws InterruptedException {
// 创建锁标记
Object a = new Object();
Object b = new Object();
Object c = new Object();
// 三个任务
ThreadPrinter pa = new ThreadPrinter("A", c, a);
ThreadPrinter pb = new ThreadPrinter("B", a, b);
ThreadPrinter pc = new ThreadPrinter("C", b, c);
// 创建三个线程分别把任务扔里面
new Thread(pa).start();
Thread.sleep(10);
new Thread(pb).start();
Thread.sleep(10);
new Thread(pc).start();
Thread.sleep(10);
}
}

法二:ReentrantLock

使用ReentrantLock替代synchronized。ReentrantLock是一个可重入且独占式的锁,它具有与使用synchronized监视器锁相同的基本行为和语义,但与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
package com.xwl;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestABC {
private static int state = 0; // 作为锁
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
Thread A = new Thread(new Runnable(){
public void run() {
while(state<=30){
lock.lock();
if(state%3 == 0){
System.out.println('A');
state++;
}
lock.unlock();
}
}
});
Thread B = new Thread(new Runnable(){
public void run() {
while(state<=30){
lock.lock();
if(state%3 == 1){
System.out.println('B');
state++;
}
lock.unlock();
}
}
});
Thread C = new Thread(new Runnable(){
public void run() {
while(state<=30){
lock.lock();
if(state%3 == 2){
System.out.println('C');
state++;
}
lock.unlock();
}
}
});
A.start();
B.start();
C.start();
}
}

参考链接

  1. dadapeng:Java两个线程实现交替运行-以交替打印奇偶数为例
  2. 之牧_牧之算法_创建三个线程交替循环打印“A”、”B”、“C”
  3. DivineH:Java并发编程之ReentrantLock详解

土豪将鼓励我继续创作和搬运!