面试题 - 使用线程交替打印奇数偶数

这世上有三样东西是别人抢不走的:一是吃进胃里的食物,二是藏在心中的梦想,三是读进大脑的书

  • 分析题目。需要使用两个线程交替打印奇偶数。
    • 使用同步锁解决这个问题
    • 使用信号量来实现交替打印
  • 定义两个信号量,一个奇数信号量,一个偶数信号量,都初始化为1
  • 先用掉偶数的信号量,因为要让奇数先启动,等奇数打印完再释放

信号量实现

  • 具体实现思路:
    • 定义两个信号量,一个奇数信号量,一个偶数信号量,都初始化为1
    • 先用掉偶数的信号量,因为要让奇数先启动,等奇数打印完再释放
    • 具体流程就是 第一次的时候先减掉偶数的信号量 奇数线程打印完成以后用掉奇数的信号量。然后释放偶数的信号量如此循环
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
import java.util.concurrent.Semaphore;

/**
* @ClassName AlternatePrinting
* @Author yunlogn
* @Date 2019/5/21
* @Description 交替打印奇偶数
*/
public class AlternatePrinting {

static int i = 0;
public static void main(String[] args) throws InterruptedException {

Semaphore semaphoreOdd = new Semaphore(1);
Semaphore semaphoreEven = new Semaphore(1);

semaphoreOdd.acquire(); //让奇数先等待启动,所以先减掉偶数的信号量 等奇数线程来释放

SemaphorePrintEven semaphorePrintEven = new SemaphorePrintEven(semaphoreOdd, semaphoreEven);
Thread t1 = new Thread(semaphorePrintEven);
t1.start();

SemaphorePrintOdd semaphorePrintOdd = new SemaphorePrintOdd(semaphoreOdd, semaphoreEven);
Thread t2 = new Thread(semaphorePrintOdd);
t2.start();

}

/**
* 使用信号量实现
*/
static class SemaphorePrintOdd implements Runnable {

private Semaphore semaphoreOdd;
private Semaphore semaphoreEven;


public SemaphorePrintOdd(Semaphore semaphoreOdd, Semaphore semaphoreEven) {
this.semaphoreOdd = semaphoreOdd;
this.semaphoreEven = semaphoreEven;
}

@Override
public void run() {
try {
           
semaphoreOdd.acquire();//获取信号量 semaphoreOdd在初始化的时候被获取了信号量所以这里被阻塞了,所以会先执行下面的奇数线程
while (true) {
i++;
if (i % 2 == 0) {
System.out.println("偶数线程:" + i);
semaphoreEven.release();//释放偶数信号量 让奇数线程那边的阻塞解除
//再次申请获取偶数信号量,因为之前已经获取过,如果没有奇数线程去释放,那么就会一直阻塞在这,等待奇数线程释放
semaphoreOdd.acquire();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

static class SemaphorePrintEven implements Runnable {


private Semaphore semaphoreOdd;
private Semaphore semaphoreEven;


public SemaphorePrintEven(Semaphore semaphoreOdd, Semaphore semaphoreEven) {
this.semaphoreOdd = semaphoreOdd;
this.semaphoreEven = semaphoreEven;
}

@Override
public void run() {

try {
         
         
semaphoreEven.acquire();
while (true) {
i++;
if (i % 2 == 1) {
System.out.println("奇数线程:" + i);
semaphoreOdd.release(); //释放奇数信号量 让偶数线程那边的阻塞解除

//这里阻塞,等待偶数线程释放信号量
//再次申请获取奇数信号量,需要等偶数线程执行完然后释放该信号量,不然阻塞
semaphoreEven.acquire();
}
}

} catch (Exception ex) {}


}
}
}

  • 需要注意的是,如果某个线程来不及释放就异常中断了,会导致另一个线程一直在等,造成死锁。 虽然这个异常不在这个问题的考虑范围内 但是可以使用finally 来包裹释放锁资源

同步锁打印

  • 让两个线程使用同一把锁。交替执行 。
    • 判断是不是奇数 如果是奇数进入奇数线程执行打印并加一。然后线程释放锁资源。然后让该线程等待
    • 判断是不是偶数,如果是偶数进入偶数线程执行打印并加一。然后线程释放锁资源。然后让该线程等待
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
import java.util.concurrent.atomic.AtomicInteger;


/**
* @ClassName AlternatePrinting
* @Author yunlogn
* @Date 2019/5/21
* @Description 交替打印奇偶数
*/
public class AlternatePrinting {

public static AtomicInteger atomicInteger = new AtomicInteger(1);

public static void main(String[] args) throws InterruptedException {

Thread a=new Thread(new AThread());
Thread b=new Thread(new BThread());
a.start();
b.start();

}


public static class AThread implements Runnable {

@Override
public void run() {
while (true) {
synchronized (atomicInteger) {
if (atomicInteger.intValue() % 2 != 0) {
System.out.println("奇数线程:" + atomicInteger.intValue());
atomicInteger.getAndIncrement();
// 奇数线程释放锁资源
atomicInteger.notify();
try {
atomicInteger.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
try {
// 奇数线程等待
atomicInteger.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}

public static class BThread implements Runnable {

@Override
public void run() {
while (true){
synchronized (atomicInteger){
if(atomicInteger.intValue() %2== 0 ){
System.out.println("偶数线程:"+ atomicInteger.intValue());
atomicInteger.getAndIncrement();
// 偶数线程释放锁资源
atomicInteger.notify();
try {
atomicInteger.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
try {
// 偶数线程等待
atomicInteger.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}

}

一种更简单的写法

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


public static void main(String[] args) {
PrintDigitThread print1 = new PrintDigitThread((i) -> i % 2 == 1, "thread1");
PrintDigitThread print2 = new PrintDigitThread((i) -> i % 2 == 0, "thread2");
print1.start();
print2.start();
}
}

class ShareData {
public static final AtomicInteger atomicInt = new AtomicInteger(0);
}

class PrintDigitThread extends Thread {
private Predicate<Integer> predicate;

public PrintDigitThread(Predicate<Integer> predicate, String name) {
this.predicate = predicate;
this.setName(name);
}

@Override
public void run() {
int v = ShareData.atomicInt.get();
while (v < 100) {
synchronized (ShareData.atomicInt) {
v = ShareData.atomicInt.get();
if (predicate.test(v)) {
System.out.println(Thread.currentThread().getName() + ":" + v);
ShareData.atomicInt.incrementAndGet();
try {
ShareData.atomicInt.notify();
} catch (Exception ex) {

}
} else {
try {
ShareData.atomicInt.wait();
} catch (Exception ex) {

}
}
}
}
}
}

欢迎关注  http://yunlongn.github.io

作者

RolandLee

发布于

2019-05-21

更新于

2021-02-21

许可协议

评论