java多线程交替打印abc_华为和阿里都考过的多线程编程题,你会吗?多线程交替打印 ABC的多种实现方法...

题目描述如下:

编写一个程序,开启三个线程,这三个线程的 ID 分别是 A、B 和 C,每个线程把自己的 ID 在屏幕上打印 10 遍,要求输出结果必须按 ABC 的顺序显示,如 ABCABCABC... 依次递推

这是一道经典的多线程编程面试题,首先吐槽一下,这道题的需求很是奇葩,先开启多线程,然后再串行打印 ABC,这不是吃饱了撑的吗?不过既然是道面试题,就不管这些了,其目的在于考察你的多线程编程基础。就这道题,你要是写不出个三四种解法,你都不好意思说你学过多线程。哈哈开玩笑,下面就为你介绍一下本题的几种解法。

1、最简单的方法——使用 LockSupport

LockSupport 是java.util.concurrent.locks包下的工具类,它的静态方法unpark()和park()可以分别实现阻塞当前线程和唤醒指定线程的效果,所以用它解决这样的问题简直是小菜一碟,代码如下:

public class PrintABC {

static Thread threadA, threadB, threadC;

public static void main(String[] args) {

threadA = new Thread(() -> {

for (int i = 0; i < 10; i++) {

// 打印当前线程名称

System.out.print(Thread.currentThread().getName());

// 唤醒下一个线程

LockSupport.unpark(threadB);

// 当前线程阻塞

LockSupport.park();

}

}, "A");

threadB = new Thread(() -> {

for (int i = 0; i < 10; i++) {

// 先阻塞等待被唤醒

LockSupport.park();

System.out.print(Thread.currentThread().getName());

// 唤醒下一个线程

LockSupport.unpark(threadC);

}

}, "B");

threadC = new Thread(() -> {

for (int i = 0; i < 10; i++) {

// 先阻塞等待被唤醒

LockSupport.park();

System.out.print(Thread.currentThread().getName());

// 唤醒下一个线程

LockSupport.unpark(threadA);

}

}, "C");

threadA.start();

threadB.start();

threadC.start();

}

}

执行结果如下:

ABCABCABCABCABCABCABCABCABCABC

Process finished with exit code 0

2、最传统的方法——使用synchronized 锁机制

这种方法就是直接使用 Java 的 synchronized 关键字,配合 Object 的 wait()和notifyAll()方法实现线程交替打印的效果,不过这种写法的复杂度和代码量都偏大。由于notify()和notifyAll()方法都不能唤醒指定的线程,所以需要三个布尔变量对线程执行顺序进行控制。另外要注意的就是,for 循环中的 i++需要在线程打印之后执行,否则每次被唤醒后,不管是不是轮到当前线程打印都会执行i++,这显然不是我们想要的。代码如下 (一般B、C线程和A线程的执行逻辑类似,只在A线程代码中进行详细注释说明):

public class PrintABC {

// 使用布尔变量对打印顺序进行控制,true表示轮到当前线程打印

private static boolean startA = true;

private static boolean startB = false;

private static boolean startC = false;

public static void main(String[] args) {

// 作为锁对象

final Object o = new Object();

// A线程

new Thread(() -> {

synchronized (o) {

for (int i = 0; i < 10; ) {

if (startA) {

// 代表轮到当前线程打印

System.out.print(Thread.currentThread().getName());

// 下一个轮到B打印,所以把startB置为true,其它为false

startA = false;

startB = true;

startC = false;

// 唤醒其他线程

o.notifyAll();

// 在这里对i进行增加操作

i++;

} else {

// 说明没有轮到当前线程打印,继续wait

try {

o.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

}, "A").start();

// B线程

new Thread(() -> {

synchronized (o) {

for (int i = 0; i < 10; ) {

if (startB) {

System.out.print(Thread.currentThread().getName());

startA = false;

startB = false;

startC = true;

o.notifyAll();

i++;

} else {

try {

o.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

}, "B").start();

// C线程

new Thread(() -> {

synchronized (o) {

for (int i = 0; i < 10; ) {

if (startC) {

System.out.print(Thread.currentThread().getName());

startA = true;

startB = false;

startC = false;

o.notifyAll();

i++;

} else {

try {

o.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

}, "C").start();

}

}

执行结果如下:

ABCABCABCABCABCABCABCABCABCABC

Process finished with exit code 0

3、使用 Lock 搭配 Condition 实现

使用 synchronized 锁机制的写法着实有些复杂,何不试试 ReentrantLock?这是java.util.concurrent.locks包下的锁实现类,它拥有更灵活的 API,能够对多线程执行流程实现更精细的控制,特别是在搭配 Condition 使用的情况下,可以随心所欲地控制多个线程的执行顺序,来看看这个组合在本题中的使用吧,代码如下:

public class PrintABC {

public static void main(String[] args) {

ReentrantLock lock = new ReentrantLock();

// 使用ReentrantLock的newCondition()方法创建三个Condition

// 分别对应A、B、C三个线程

Condition conditionA = lock.newCondition();

Condition conditionB = lock.newCondition();

Condition conditionC = lock.newCondition();

// A线程

new Thread(() -> {

try {

lock.lock();

for (int i = 0; i < 10; i++) {

System.out.print(Thread.currentThread().getName());

// 叫醒B线程

conditionB.signal();

// 本线程阻塞

conditionA.await();

}

// 这里有个坑,要记得在循环之后调用signal(),否则线程可能会一直处于

// wait状态,导致程序无法结束

conditionB.signal();

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

// 在finally代码块调用unlock方法

lock.unlock();

}

}, "A").start();

// B线程

new Thread(() -> {

try {

lock.lock();

for (int i = 0; i < 10; i++) {

System.out.print(Thread.currentThread().getName());

conditionC.signal();

conditionB.await();

}

conditionC.signal();

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

lock.unlock();

}

}, "B").start();

// C线程

new Thread(() -> {

try {

lock.lock();

for (int i = 0; i < 10; i++) {

System.out.print(Thread.currentThread().getName());

conditionA.signal();

conditionC.await();

}

conditionA.signal();

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

lock.unlock();

}

}, "C").start();

}

}

执行结果如下:

ABCABCABCABCABCABCABCABCABCABC

Process finished with exit code 0

4、使用 Semaphore 实现

semaphore中文意思是信号量,原本是操作系统中的概念,JUC下也有个 Semaphore 的类,可用于控制并发线程的数量。Semaphore 的构造方法有个 int 类型的 permits 参数,如下:

public Semaphore(int permits) {...}

其中 permits 指的是该 Semaphore 对象可分配的许可数,一个线程中的 Semaphore 对象调用acquire()方法可以让线程获取许可继续运行,同时该对象的许可数减一,如果当前没有可用许可,线程会阻塞。该 Semaphore 对象调用release()方法可以释放许可,同时其许可数加一。Talk is cheap, show me the code!

public class PrintABC {

public static void main(String[] args) {

// 初始化许可数为1,A线程可以先执行

Semaphore semaphoreA = new Semaphore(1);

// 初始化许可数为0,B线程阻塞

Semaphore semaphoreB = new Semaphore(0);

// 初始化许可数为0,C线程阻塞

Semaphore semaphoreC = new Semaphore(0);

new Thread(() -> {

for (int i = 0; i < 10; i++) {

try {

// A线程获得许可,同时semaphoreA的许可数减为0,进入下一次循环时

// A线程会阻塞,知道其他线程执行semaphoreA.release();

semaphoreA.acquire();

// 打印当前线程名称

System.out.print(Thread.currentThread().getName());

// semaphoreB许可数加1

semaphoreB.release();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}, "A").start();

new Thread(() -> {

for (int i = 0; i < 10; i++) {

try {

semaphoreB.acquire();

System.out.print(Thread.currentThread().getName());

semaphoreC.release();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}, "B").start();

new Thread(() -> {

for (int i = 0; i < 10; i++) {

try {

semaphoreC.acquire();

System.out.print(Thread.currentThread().getName());

semaphoreA.release();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}, "C").start();

}

}

执行结果如下:

ABCABCABCABCABCABCABCABCABCABC

Process finished with exit code 0

5、总结

本文一共介绍了四种三个线程交替打印的实现方法,其中第一种方法最简单易懂,但是更能考察多线程编程功底的应该是第二和第三种方法,在面试中也更加分。只要把这几种方法熟练掌握并彻底理解,以后碰到此类题型就不用慌了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值