java多线程编程之连续打印abc的几种解法

亡梦爱人 提交于 2019-12-06 19:49:11

一道编程题如下:

实例化三个线程,一个线程打印a,一个线程打印b,一个线程打印c,三个线程同时执行,要求打印出10个连着的abc。

题目分析:

通过题意我们可以得出,本题需要我们使用三个线程,三个线程分别会打印6次字符,关键是如何保证顺序一定是abc...呢。所以此题需要同步机制来解决问题!

令打印字符A的线程为ThreadA,打印B的ThreadB,打印C的为ThreadC。问题为三线程间的同步唤醒操作,主要的目的就是使程序按ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程,因此本人整理出了三种方式来解决此问题。

最初给个错误示例:

public class MyABC {
	public static void main(String[] args) {
		MyRun run = new MyRun();
		Thread a = new Thread(run, "A");
		Thread b = new Thread(run, "B");
		Thread c = new Thread(run, "C");
		a.start();
		b.start();
		c.start();
	}
}

class MyRun implements Runnable {

	private int total = 1;
	private boolean goA = true;
	private boolean goB = false;
	private boolean goC = false;

	@Override
	public void run() {
		while (total <= 10) {
			if (Thread.currentThread().getName().equals("A") && goA) {
				System.out.print(Thread.currentThread().getName());
				// 可试着用sleep来模拟CPU分配时间,看看是什么结果
//				Thread.currentThread().sleep(100);
//				synchronized (this) {
					goB = true;
					goA = false;
//				}
			} else if (Thread.currentThread().getName().equals("B") && goB) {
				System.out.print(Thread.currentThread().getName());
				// 可试着用sleep来模拟CPU分配时间,看看是什么结果
//				Thread.currentThread().sleep(100);
					goC = true;
					goB = false;
			} else if (Thread.currentThread().getName().equals("C") && goC) {
				System.out.print(Thread.currentThread().getName());
				// 可试着用sleep来模拟CPU分配时间,看看是什么结果
//				Thread.currentThread().sleep(100);
				goA = true;
				goC = false;
				total++;
			}
		}
	}
}

解析:

该代码严格依赖各个标志,一旦标志变换顺序被打乱,程序就崩了。线程的执行是由系统随机调度的,也就是当线程A执行完gob=true时,很有可能CPU被系统收回,此时B执行,C执行,当C执行到goa=true时,假设系统把C的CPU收回,分配给A,而A得到CPU权限后继续下一条代码goa=false;此时标志位乱了,程序无法继续正常执行。因为线程是随机的,所有还有很多可能。。。别的不说,最少要在两个状态那里加上synchronized 包裹

下面给出正确的做法:

一、通过两个锁(不推荐,可读性和安全性比较差)

(1)解法1:

/**
 * 基于两个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均可,因为同一时刻只有一个线程在等待(因为每个线程只有自身的锁和上一个锁,不会出现a,b,c同时抢一个锁的情况)
                    thisLock.notify();
                }
                // 不是最后一次则通过fontLock等待被唤醒
                // 必须要加判断,不然虽然能够打印10次,但10次后就会直接死锁
                //最后一次打印的时候线程A唤醒线程B,A线程等待;线程B唤醒线程C,线程B等待;线程C唤醒线程A,线程C等待(此时只有A线程不会被阻塞,线程BC会被阻塞)
                if(i < PRINT_COUNT - 1){
                    try {
                        // 通过fontLock等待被唤醒
                        fontLock.wait();
                        
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //如果不加if(i < PRINT_COUNT - 1)的条件可以这么做:因为线程A不阻塞,而线程B在等待A锁,线程C在等待B锁,所以这里可以通过线程A的notify()释放A锁,来唤醒B线程
                synchronized (thisLock) {
                	thisLock.notify();
                }
            }    
        }    
    }

    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)。

通俗点说:就是每个对象只有一把锁,即lockA 一把锁,lockB一把锁,lockC一把锁,其中想进入同步代码块,执行打印,必须要具有两把锁,即threadA要执行打印,要具备lockC和lockA,threadB要执行打印,必须具备lockA和lockB,threadC要具备lockB和lockC,而且获取两把锁的顺序要一致,即获取到第一把锁之后,才能去争取第二把锁(如果不能获取第一把锁,那么取争取第二把锁的机会也没有,就不能执行run方法),线程A启动之后,在main主线程执行sleep休眠之后,获得cpu时间片开始执行run方法,此时获得lockC之后,再获得lockA,进入同步代码块,此时执行threadA执行thisLock.notify();唤醒外面等待的lockA锁的线程(这个时候B线程还没有执行,所以外面没有线程等待lockA锁,如果B线程启动之后,在lockA锁池中等待,那么执行notify()方法,会立即唤醒threadB线程,去参与lockA锁的争夺,但是notify()只是唤醒,并不会释放锁(只有在执行完synchronized (thisLock)的代码之后,才能释放thisLock的锁,很明显执行了thisLock.notify(),synchronized (thisLock)锁住的代码就已经结束,所以thisLock.notify()会立即唤醒B线程,同时释放lockB的锁),只有在threadA调用fontLock.wait()之后(线程执行完同步代码块也会释放锁),会立即释放自身持有的lockC锁(但是此时lockA锁并没有释放),并进入等待,等待持有lockC锁的线程的唤醒,即threadC线程,其他代码执行过程分析省略...

注意:一个线程通过synchronized嵌套锁住多个对象,然后在最里层调用wait()函数,只释放wait()函数关联的锁对象,而不是释放线程当时持有的全部锁(对象的wait()函数,只会释放该对象的锁,不影响其他的锁的状态)。

(2)解法2:

/**
 * 基于两个lock实现连续打印abcabc....
 * @author lixiaoxi
 *
 */
public class MyThreadPrinter2 implements Runnable {

	private String name;
	private Object prev;
	private Object self;

	private MyThreadPrinter2(String name, Object prev, Object self) {
		this.name = name;
		this.prev = prev;
		this.self = self;
	}

	@Override
	public void run() {
		int count = 10;
		while (count > 0) {
			synchronized (prev) {
				synchronized (self) {
					System.out.print(name);
					count--;
					//通知等待self锁的线程可以抢锁了,即唤醒等待self锁的线程,即后一个线程,但此时并没有释放self的锁,只有执行完synchronized (self) {...}代码后才会释放self的锁
					self.notify();
				}
				try {
					prev.wait();//当前线程持有的prev的锁被释放,同时当前线程进入等待被唤醒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

		}

		//如果缺失下面这段代码,那么会有两个线程无法退出,因为输出A的线程打印10次后,没办法调用notify()通知打印B的线程,以此内推
//		synchronized (self) {
//			/**
//			  *线程A被唤醒,进入最后一次打印A的时候,self.notify()会唤醒等待A锁(self)的线程(即B线程),通过prev.wait()会释放线程A持有的C锁,当前线程A等待被唤醒
//			  *线程B被唤醒,进入最后一次打印B的时候,self.notify()会唤醒等待B锁(self)的线程(即C线程),通过prev.wait()会释放线程B持有的A锁,此时线程B等待被唤醒
//			  *线程C被唤醒,进入最后一次打印C的时候,self.notify()会唤醒等待C锁(self)的线程(即A线程),通过prev.wait()会释放线程C持有的B锁,此时线程C等待被唤醒
//			 *总结:如果缺失这段代码,线程A会被唤醒,但是BC会阻塞,形成死锁
//		        *加入这段代码的效果是:最后一次线程C在执行了prev.wait()之后,会释放自身持有的A锁,此时线程A被唤醒(B,C在等待被唤醒),继续执行,进入到while(){}下面的synchronized (self) {}
//		        *此时通过self.notify();会唤醒等待A锁的线程(B线程),在线程A执行完run()方法后,A线程会释放自身持有的A锁,此时B线程抢到A锁,继续执行while后的同步代码块,同理B线程会唤醒C线程
//			 */
//			self.notify();
//		}
//		System.out.print(Thread.currentThread().getName()+"---执行完毕");
	}

	public static void main(String[] args) throws Exception {
		Object a = new Object();
		Object b = new Object();
		Object c = new Object();
		MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
		MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
		MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);

		new Thread(pa).start();
		Thread.sleep(10);  
		new Thread(pb).start();
		Thread.sleep(10);  
		new Thread(pc).start();
	}
}

先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。

为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。

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

运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。

看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。

但是这种假设依赖于JVM中线程调度、执行的顺序。具体来说就是,在main主线程启动ThreadA后,需要在ThreadA执行完,在prev.wait()等待时,再切回线程启动ThreadB,ThreadB执行完,在prev.wait()等待时,再切回主线程,启动ThreadC,只有JVM按照这个线程运行顺序执行,才能保证输出的结果是正确的。而这依赖于JVM的具体实现。

考虑一种情况,如下:如果主线程在启动A后,在执行A的同步代码块的过程中又切回主线程(假设在代码self.notify之前就被切回主线程),启动了ThreadB,ThreadC,之后,由于ThreadA尚未执行self.notify,也就是ThreadB需要在synchronized(prev)处等待,而ThreadB因为等待prev的锁(A锁),而无法进入synchronized (self),所以线程B此时没有持有自身的B锁(self),而这时C却调用synchronized(prev)获取了对b的对象锁。这样,在A调用完后,同时ThreadB获取了prev也就是a的对象锁,而ThreadC持有B锁,ThreadA也释放了C锁,此时ThreadC的执行条件就已经满足了,会打印C,之后释放c,及b的对象锁,这时ThreadB具备了运行条件,会打印B,也就是循环变成了ACBACB了。这种情况,可以通过在run中主动释放CPU,来进行模拟(即在self.notify之前使用Thread.sleep(1)即可让出cpu时间片,让其他线程执行)。为了避免这种与JVM调度有关的不确定性。需要让A,B,C三个线程以确定的顺序启动,在线程A和线程B启动之后,各加入:Thread.sleep(10); 

(3)用concurrent包实现的同样的逻辑代码

/**
 * 基于两个lock实现连续打印abcabc.... concurrent的逻辑代码
 * 
 * @author lixiaoxi
 *
 */
public class MyThreadPrinter2 implements Runnable {

	private String name;
	private ReentrantLock prev;
	private ReentrantLock self;
	private Condition prevCondition;
	private Condition selfCondition;

	private MyThreadPrinter2(String name, ReentrantLock prev, Condition prevCondition, ReentrantLock self,
			Condition selfCondition) {
		this.name = name;
		this.prev = prev;
		this.self = self;
		this.prevCondition = prevCondition;
		this.selfCondition = selfCondition;
	}

	public void run() {
		int count = 10;
		while (count > 0) {
			prev.lock();
			self.lock();
			System.out.print(name);
			count--;
			selfCondition.signal();
			self.unlock();
			try {
				prevCondition.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				prev.unlock();
			}
		}
		self.lock();
		selfCondition.signal();
		self.unlock();
	}

	public static void main(String[] args) throws Exception {
		ReentrantLock a = new ReentrantLock();
		Condition aCondition = a.newCondition();
		ReentrantLock b = new ReentrantLock();
		Condition bCondition = b.newCondition();
		ReentrantLock c = new ReentrantLock();
		Condition cCondition = c.newCondition();
		MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, cCondition, a, aCondition);
		MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, aCondition, b, bCondition);
		MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, bCondition, c, cCondition);

		new Thread(pa).start();
		Thread.sleep(10);
		new Thread(pb).start();
		Thread.sleep(10);
		new Thread(pc).start();
		Thread.sleep(10);
	}
}

二、通过一个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);
        //printerA.join();
        printerB.start();
        Thread.sleep(100);
        printerC.start();
    }
}

打印结果:

ABCABCABCABCABCABCABCABCABCABC

分析:

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

注意:这里不能使用join(),因为join()方法会让主线程等待printerA线程执行完毕,而线程中有await()方法,会一直阻塞...

下面分析下线程启动的过程:
调用start()方法后,线程printerA启动,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法;此时主线程继续向下执行, Thread.sleep(100)让主线程阻塞此时main线程让出CPU资源,printerA获得cpu时间片,开始执行run方法,调用reentrantLock.lock();获取锁资源,进入同步方法,在printerA执行到thisCondtion.await()的时候,释放锁资源并等待被唤醒;主线程main在sleep在休眠时间过后的某个点获取cpu资源后,继续向下执行,此时printerB线程启动(此时如果printerA如果还没有执行到await()方法,则在等待;如果printerA已经执行了await(),则printerB获取锁资源,进入同步方法),以下C线程同理..
nextCondtion.signal();唤醒是因为等待了thisCondtion.await();如果不写nextCondtion.signal(),那么只要等待了就没有唤醒的机会了...

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

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();
    }
}

运行结果:

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

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!