Java多线程连续打印abc实现方法详解

一道编程题如下:

实例化三个线程,一个线程打印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;

// 下一个线程打印所需要的condition

private final Condition nextCondtion;

// 打印字符

private final char printChar;

public RcSyncPrinter(ReentrantLock reentrantLock, Condition thisCondtion, Condition nextCondition,

char printChar) {

this.reentrantLock = reentrantLock;

this.nextCondtion = nextCondition;

this.thisCondtion = thisCondtion;

this.printChar = printChar;

}

@Override

public void run() {

// 获取打印锁 进入临界区

reentrantLock.lock();

try {

// 连续打印PRINT_COUNT次

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

//打印字符

System.out.print(printChar);

// 使用nextCondition唤醒下一个线程

// 因为只有一个线程在等待,所以signal或者signalAll都可以

nextCondtion.signal();

// 不是最后一次则通过thisCondtion等待被唤醒

// 必须要加判断,不然虽然能够打印10次,但10次后就会直接死锁

if (i < PRINT_COUNT - 1) {

try {

// 本线程让出锁并等待唤醒

thisCondtion.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

} finally {

// 释放打印锁

reentrantLock.unlock();

}

}

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

// 写锁

ReentrantLock lock = new ReentrantLock();

// 打印a线程的condition

Condition conditionA = lock.newCondition();

// 打印b线程的condition

Condition conditionB = lock.newCondition();

// 打印c线程的condition

Condition conditionC = lock.newCondition();

// 实例化A线程

Thread printerA = new Thread(new RcSyncPrinter(lock, conditionA, conditionB, 'A'));

// 实例化B线程

Thread printerB = new Thread(new RcSyncPrinter(lock, conditionB, conditionC, 'B'));

// 实例化C线程

Thread printerC = new Thread(new RcSyncPrinter(lock, conditionC, conditionA, 'C'));

// 依次开始A B C线程

printerA.start();

Thread.sleep(100);

printerB.start();

Thread.sleep(100);

printerC.start();

}

}

打印结果:

ABCABCABCABCABCABCABCABCABCABC

分析:

仔细想想本问题,既然同一时刻只能有一个线程打印字符,那我们为什么不使用一个同步锁ReentrantLock?线程之间的唤醒操作可以通过Condition实现,且Condition可以有多个,每个condition.await阻塞只能通过该condition的signal/signalall来唤醒!这是synchronized关键字所达不到的,那我们就可以给每个打印线程一个自身的condition和下一个线程的condition,每次打印字符后,调用下一个线程的condition.signal来唤醒下一个线程,然后自身再通过自己的condition.await来释放锁并等待唤醒。

三、通过一个锁和一个状态变量来实现(推荐)

package com.demo.test;

/**

* 基于一个锁和一个状态变量实现连续打印abcabc...

* @author lixiaoxi

*

*/

public class StateLockPrinter {

//状态变量

private volatile int state=0;

// 打印线程

private class Printer implements Runnable {

//打印次数

private static final int PRINT_COUNT=10;

//打印锁

private final Object printLock;

//打印标志位 和state变量相关

private final int printFlag;

//后继线程的线程的打印标志位,state变量相关

private final int nextPrintFlag;

//该线程的打印字符

private final char printChar;

public Printer(Object printLock, int printFlag,int nextPrintFlag, char printChar) {

super();

this.printLock = printLock;

this.printFlag=printFlag;

this.nextPrintFlag=nextPrintFlag;

this.printChar = printChar;

}

@Override

public void run() {

//获取打印锁 进入临界区

synchronized (printLock) {

//连续打印PRINT_COUNT次

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

//循环检验标志位 每次都阻塞然后等待唤醒

while (state!=printFlag) {

try {

printLock.wait();

} catch (InterruptedException e) {

return;

}

}

//打印字符

System.out.print(printChar);

//设置状态变量为下一个线程的标志位

state=nextPrintFlag;

//注意要notifyall,不然会死锁,因为notify只通知一个,

//但是同时等待的是两个,如果唤醒的不是正确那个就会没人唤醒,死锁了

printLock.notifyAll();

}

}

}

}

public void test() throws InterruptedException{

//锁

Object lock=new Object();

//打印A的线程

Thread threadA=new Thread(new Printer(lock, 0,1, 'A'));

//打印B的线程

Thread threadB=new Thread(new Printer(lock, 1,2, 'B'));

//打印C的线程

Thread threadC=new Thread(new Printer(lock, 2,0, 'C'));

//一次启动A B C线程

threadA.start();

Thread.sleep(1000);

threadB.start();

Thread.sleep(1000);

threadC.start();

}

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

StateLockPrinter print = new StateLockPrinter();

print.test();

}

}

打印结果:

ABCABCABCABCABCABCABCABCABCABC

分析:

状态变量是一个volatile的整型变量,0代表打印a,1代表打印b,2代表打印c,三个线程都循环检验标志位,通过阻塞前和阻塞后两次判断可以确保当前打印的正确顺序,随后线程打印字符,然后设置下一个状态字符,唤醒其它线程,然后重新进入循环。

补充题

三个Java多线程循环打印递增的数字,每个线程打印5个数值,打印周期1-75,同样的解法:

package com.demo.test;

import java.util.concurrent.atomic.AtomicInteger;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.ReentrantLock;

/**

* 数字打印,三个线程同时打印数字,第一个线程打印12345,第二个线程打印678910 .........

* @author lixiaoxi

*

*/

public class NumberPrinter {

//打印计数器

private final AtomicInteger counter=new AtomicInteger(0);

private class Printer implements Runnable{

//总共需要打印TOTAL_PRINT_COUNT次

private static final int TOTAL_PRINT_COUNT = 5;

//每次打印PER_PRINT_COUNT次

private static final int PER_PRINT_COUNT = 5;

//打印锁

private final ReentrantLock reentrantLock;

//前一个线程的condition

private final Condition afterCondition;

//本线程的condition

private final Condition thisCondtion;

public Printer(ReentrantLock reentrantLock, Condition thisCondtion,Condition afterCondition) {

super();

this.reentrantLock = reentrantLock;

this.afterCondition = afterCondition;

this.thisCondtion = thisCondtion;

}

@Override

public void run() {

//进入临界区

reentrantLock.lock();

try {

//循环打印TOTAL_PRINT_COUNT次

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

//打印操作

for(int j=0;j<PER_PRINT_COUNT;j++){

//以原子方式将当前值加 1。

//incrementAndGet返回的是新值(即加1后的值)

System.out.println(counter.incrementAndGet());

}

//通过afterCondition通知后面线程

afterCondition.signalAll();

if(i < TOTAL_PRINT_COUNT - 1){

try {

//本线程释放锁并等待唤醒

thisCondtion.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

} finally {

reentrantLock.unlock();

}

}

}

public void test() throws InterruptedException {

//打印锁

ReentrantLock reentrantLock=new ReentrantLock();

//打印A线程的Condition

Condition conditionA=reentrantLock.newCondition();

//打印B线程的Condition

Condition conditionB=reentrantLock.newCondition();

//打印C线程的Condition

Condition conditionC=reentrantLock.newCondition();

//打印线程A

Thread threadA=new Thread(new Printer(reentrantLock,conditionA, conditionB));

//打印线程B

Thread threadB=new Thread(new Printer(reentrantLock, conditionB, conditionC));

//打印线程C

Thread threadC=new Thread(new Printer(reentrantLock, conditionC, conditionA));

// 依次开启a b c线程

threadA.start();

Thread.sleep(100);

threadB.start();

Thread.sleep(100);

threadC.start();

}

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

NumberPrinter print = new NumberPrinter();

print.test();

}

}

运行结果:

2

4

6

8

10

12

14

16

18

20

22

24

26

28

30

32

34

36

38

40

42

44

46

48

50

52

54

56

58

60

62

64

66

68

70

72

74

以上是 Java多线程连续打印abc实现方法详解 的全部内容, 来源链接: utcz.com/z/328094.html

回到顶部