一道编程题如下:
实例化三个线程,一个线程打印a,一个线程打印b,一个线程打印c,三个线程同时执行,要求打印出10个连着的abc。
题目分析:
通过题意我们可以得出,本题需要我们使用三个线程,三个线程分别会打印6次字符,关键是如何保证顺序一定是abc...呢。所以此题需要同步机制来解决问题!
令打印字符A的线程为ThreadA,打印B的ThreadB,打印C的为ThreadC。问题为三线程间的同步唤醒操作,主要的目的就是使程序按ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程,因此本人整理出了三种方式来解决此问题。
一、通过两个锁(不推荐,可读性和安全性比较差)
package com.demo.test;
/**
* 基于两个lock实现连续打印abcabc....
* @author lixiaoxi
*
*/
public class TwoLockPrinter implements Runnable {
// 打印次数
private static final int PRINT_COUNT = 10;
// 前一个线程的打印锁
private final Object fontLock;
// 本线程的打印锁
private final Object thisLock;
// 打印字符
private final char printChar;
public TwoLockPrinter(Object fontLock, Object thisLock, char printChar) {
this.fontLock = fontLock;
this.thisLock = thisLock;
this.printChar = printChar;
}
@Override
public void run() {
// 连续打印PRINT_COUNT次
for (int i = 0; i < PRINT_COUNT; i++) {
// 获取前一个线程的打印锁
synchronized (fontLock) {
// 获取本线程的打印锁
synchronized (thisLock) {
//打印字符
System.out.print(printChar);
// 通过本线程的打印锁唤醒后面的线程
// notify和notifyall均可,因为同一时刻只有一个线程在等待
thisLock.notify();
}
// 不是最后一次则通过fontLock等待被唤醒
// 必须要加判断,不然虽然能够打印10次,但10次后就会直接死锁
if(i < PRINT_COUNT - 1){
try {
// 通过fontLock等待被唤醒
fontLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
// 打印A线程的锁
Object lockA = new Object();
// 打印B线程的锁
Object lockB = new Object();
// 打印C线程的锁
Object lockC = new Object();
// 打印a的线程
Thread threadA = new Thread(new TwoLockPrinter(lockC, lockA, 'A'));
// 打印b的线程
Thread threadB = new Thread(new TwoLockPrinter(lockA, lockB, 'B'));
// 打印c的线程
Thread threadC = new Thread(new TwoLockPrinter(lockB, lockC, 'C'));
// 依次开启a b c线程
threadA.start();
Thread.sleep(100); // 确保按顺序A、B、C执行
threadB.start();
Thread.sleep(100);
threadC.start();
Thread.sleep(100);
}
}
打印结果:
ABCABCABCABCABCABCABCABCABCABC
分析:
此解法为了为了确定唤醒、等待的顺序,每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是fontLock,就是前一个线程所持有的对象锁,还有一个就是自身对象锁thisLock。主要的思想就是,为了控制执行的顺序,必须要先持有fontLock锁,也就是前一个线程要释放掉前一个线程自身的对象锁,当前线程再去申请自身对象锁,两者兼备时打印,之后首先调用thisLock.notify()释放自身对象锁,唤醒下一个等待线程,再调用fontLock.wait()释放prev对象锁,暂停当前线程,等待再次被唤醒后进入循环。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序,所以需要手动控制他们三个的启动顺序,即Thread.Sleep(100)。
二、通过一个ReentrantLock和三个conditon实现(推荐,安全性,性能和可读性较高)
package com.demo.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 基于一个ReentrantLock和三个conditon实现连续打印abcabc...
* @author lixiaoxi
*
*/
public class RcSyncPrinter implements Runnable{
// 打印次数
private static final int PRINT_COUNT = 10;
// 打印锁
private final ReentrantLock reentrantLock;
// 本线程打印所需的condition
private final Condition thisCondtion;
// 下一个&O6&O6'/:3k{&7J3b{B;"Z>&7&O6j??B;/&O6_B;*_K/B;7Z4(`4(k/&O6K{jV_?/&O6V&O6Fr|3j4(驅4)4)й%4)й
йI4(4(V_&O6'/B3^&O6V_/&O6/&O64((4(4)94(&O6Vf4(%%4(AIr&O6=Q1}AI%9Q}
=U9S4(ЁQ=Q1}AI%9Q}
=U9P4(?&O6I}AI%9Q}
=U9S4(ЁAI}AI%9Q}
=U9P4(&O6R4(I&7/j4(
r/j4(
4(AI
(=4(V324(&O6=Q1}AI%9Q}
=U9S4(ЁQ=Q1}AI%9Q}
=U9P&O6N7ЁAI}AI%9Q}
=U9P:CZ?7ǎ4(jbZ6*ǖB;j4(MРkk~B;v,4(Q=Q1}AI%9Q}
=U9P4(r/+R'H4(Рэ%хQ4(4(%&O6R4(II&O6/j
4(
&O6/j
4(
&O6/j
4(
4(&O6-4(QQA&O6-4(QQA&O6-4(QQA,4(QQ4(Mmt%99й4(zs4(4((((4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(4(brZj3ro疒j惚r'&*rokkR2 |