控制多线程的打印顺序---交替打印(3种方式)

方法1:
package com.itheima.bookcurrentment;

分析:
/*
前提:线程123分别输出abc
需求:交替打印abc5次,打印结果示例:abcabcabcabc…
思路:用同步方法,定义多个条件,满足条件时打印,不满足时进入wait等待

设置一个整数,当数为1时打印1,当不是1时(相当于条件不满足)则进入wait等待

   输出内容  等待标记    下一个标记
     a        1           2
     b        2           3
     c        3           1

*/

@Slf4j(topic = "c.TestPrint1")
public class TestPrint1 {
    public static void main(String[] args) {
    //测试:由于要共用同一个WaitNotify对象,因此先创建一个WaitNotify对象,以便后面对它进行加锁
    // 创建三个线程,在这个三个线程中调用打印方法,并且在该方法中指定打印内容,等待标记(公共标记与这个等待标记一致则打印内容),以及下一个标记
     WaitNotify waitNotify=new WaitNotify(1,5);//初始标记为1,循环次数是5
        new Thread(()->{
            try {
                waitNotify.print("a",1,2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                waitNotify.print("b",2,3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                waitNotify.print("c",3,1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

    }
}

class WaitNotify{

    //打印方法,该方法由不同的线程调用,不同线程输出的内容,等待标记和下一个标记都不相同,因此将这三个值作为参数
    public void  print(String str,int waitFlag,int nextFlag) throws InterruptedException {
         //打印逻辑:加锁,先获取到调用线程的标记,与公共标记相比较,相等则继续打印相应内容,
        // 并设置下一个标记(唤醒下一个线程),不相等则进入阻塞
       for(int i=0;i<loopNumber;i++){
           synchronized (this){
               while (flag!=waitFlag){
                   this.wait();
               }
               System.out.print(str);
               flag=nextFlag;
               this.notifyAll();
           }
       }
    }
    //等待标记(公共标记)
    private int flag;
    //循环次数
    private int loopNumber;
    //初始化时可以设置等待标记和初始次数
    public WaitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }
}

打印结果:abcabcabcabcabc

关键点:使用了一个整数标记来判断是应该等待还是继续向下运行,并且使用了下一个等待标记控制接下来应该是哪一个线程放弃等待,执行打印。

方法2:使用ReentrantLock实现,充分利用这个锁的特点----它的条件变量(休息室)可以有多个

@Slf4j(topic = "c.TestPrint2")
public class TestPrint2 {
    public static void main(String[] args) throws InterruptedException {
     AwaitSignal awaitSignal=new AwaitSignal(5);
     //创建了3个休息室
        Condition a=awaitSignal.newCondition();
        Condition b=awaitSignal.newCondition();
        Condition c=awaitSignal.newCondition();

        new Thread(()->{
          awaitSignal.print("a",a,b);//该线程打印a,打印之前首先进入休息室a等待,当它执行完成后要唤醒b休息室的线程

        }).start();

        new Thread(()->{
            awaitSignal.print("b",b,c);//该线程打印b,打印之前首先进入休息室b等待,当它执行完成后要唤醒的c休息的线程
        }).start();
         new Thread(()->{
             awaitSignal.print("c",c,a);//该线程打印c,打印之前首先进入休息室b等待,当它执行完成后要唤醒的a休息室的线程

         }).start();

       //由于这三个线程一启动都进入了休息室等待,因此需要一个线程先唤醒a休息室中的线程
        TimeUnit.SECONDS.sleep(1);
        awaitSignal.lock();//获取锁
        try{
            log.debug("由主线程开始唤醒a线程后,,,,开始。。。。。。。。");
          a.signal();//唤醒a休息室的线程
        }finally {
            awaitSignal.unlock();//释放锁
        }
    }
}

class AwaitSignal extends ReentrantLock{
    private int loopNumber;

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    //写一个print,由三个线程调用,当不满足条件时进入格各自的休息室等待
    //参数1:打印的内容,参数2,进入哪一间休息室,参数3,表示下一件休息室
    public void print(String str,Condition current,Condition next){
        for(int i=0;i<loopNumber;i++){
            lock();//继承了ReentrantLock,可以省略前面的this
            try{
               current.await();//获得锁之后,先进入休息室等待被唤醒时表示可以继续运行执行它的打印了
                System.out.print(str);
                next.signal();//打印完成之后去唤醒下一件休息室的线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                unlock();
            }
        }
    }
}

打印结果:
19:51:44.940 [main] DEBUG c.TestPrint2 - 由主线程开始唤醒a线程后,,,,开始。。。。。。。。
abcabcabcabcabc

方式3:使用park()和unpark()实现

//这两个方法的特点是它没有对象锁的概念,也没有ReentrantLock锁的概念,其他的休息室之类的都没有了
//它去停止和恢复线程的运行都是以线程自身为单位的,所以它的实现更为简单

@Slf4j(topic = "c.TestPtint3")
public class TestPrint3 {
    static Thread t1;
    static Thread t2;
    static Thread t3;
    public static void main(String[] args) {
    parkUnpark parkUnpark=new parkUnpark(5);
   t1= new Thread(()->{
       parkUnpark.print("a",t2);//线程t1,要打印的是a,要唤醒的是t2
    });
       t2= new Thread(()->{
            parkUnpark.print("b",t3);//线程t2,要打印的是b,要唤醒的是t3
        });
        t3= new Thread(()->{
            parkUnpark.print("c",t1);//线程t3,要打印的是c,要唤醒的是t1
        });
        t1.start();
        t2.start();
        t3.start();
        //这三个线程启动后就park(暂停)住了,需要主线程来唤醒t1,让这些线程继续往下执行
        LockSupport.unpark(t1);
    }
}
class parkUnpark{
    private int loopNumber;
    //参数1:要打印的内容,参数2:要唤醒的下一个线程(需要暂停哪个,因为它是基于线程的,执行park就是暂停本线程)
    public void print(String str,Thread next){
     for(int i=0;i<loopNumber;i++){
         //每次进入循环是,先让当前线程park(暂停下来)
         LockSupport.park();//暂停当前线程
         System.out.print(str);//打印
         LockSupport.unpark(next);//唤醒下一个线程
     }
    }
    public parkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }
}

打印结果:abcabcabcabcabc

你可能感兴趣的:(控制多线程的打印顺序---交替打印(3种方式))