【手撕代码】多个线程交替打印

目录
方法一: 锁实现【推荐】:通过ReentrantLock和当前打印状态state(打印到哪了)
方法二:使用 Lock / Condition + state实现:即 A打印后唤醒等待在conditionB上的B 线程-> B打印后唤醒等待在conditionC上的C线程->C打印后唤醒等待在conditionA上的A线程(和法一类似,只不过在获取锁后,又不该它打印时,它选择await()放弃锁进入等待队列直到被唤醒,而法一它放弃锁后会再去抢锁)
方法三:信号量 semaphore 实现【推荐】:即 A打印后释放信号量B-> B打印后释放信号量C->C 打印后释放信号量A
方法四: wait / notify 实现[使用 synchronized 保证线程安全,在调用wait(), notify()或notifyAll()的时候,必须先获得锁]

转https://blog.csdn.net/pcwl1206/article/details/89429548
有三个线程轮流执行,第一个线程打印A,第二个线程打印B,第三个线程打印C……循环10次。
方法一: 锁实现【推荐】:通过ReentrantLock和当前打印状态state(打印到哪了) times:保证每个线程的打印次数;
state:保证每个线程之间的交替打印;依次来,state%3==0时只有A能打印,==1时只有B能打印,==2时只有C能打印
lock:保证每次只有一个线程能够获取到资源。

import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { private static int times = 10; // 控制打印次数 private static int state = 0; // 当前状态值:保证三个线程之间交替打印 private static Lock lock = new ReentrantLock(); // 保证每次只有一个线程能够拿到资源static class MyThread extends Thread{ private int id; private String s; public MyThread(int id,String s){ this.id = id; this.s = s; }@Override public void run() { for(int i = 0; i< times; ){ lock.lock(); try { if(state % 3 == id){ //state从0 开始,只有轮到我的时候才开始打印 System.out.println(id+" 第" + i+ "次打印" +s); state++; //打印后状态要加,比如由0 变成1 ,那么这时只有 B 能打印了,即轮到谁了 i++; //我的第 i 次打印; } }finally { lock.unlock(); } }} }public static void main(String[] args) { Thread thread1 = new MyThread(0,"A"); Thread thread2 = new MyThread(1,"B"); Thread thread3 = new MyThread(2,"C"); thread1.start(); thread2.start(); thread3.start(); } }

用Lambda表达式简写:
public class PrintABCUsingLock{ private int times; // 控制打印次数 private int state; // 当前状态值:保证三个线程之间交替打印,增加了,就换下一个线程 private Lock lock = new ReentrantLock(); // 保证每次只有一个线程能够拿到资源public PrintABCUsingLock(int times){ this.times = times; } public void printA(){ printABC("A",0); //state%3== 0的时候 A线程才打印A }public void pringB(){ printABC("B",1); } public void pringC(){ printABC("C",2); }private void printABC(String s, int targetState) { for (int i =0; i< times; ){ lock.lock(); try { if(state%3 == targetState){ System.out.println("线程"+ Thread.currentThread()+ ": "+ s+ "第"+i+"次打印"); state++; //每次打印增加state,下一次打印就只能是某个固定的线程来打印 i++; } }finally { lock.unlock(); } } }public static void main(String[] args) { PrintABCUsingLock printABCUsingLock = new PrintABCUsingLock(10); new Thread(printABCUsingLock::printA).start(); //方法引用,调用的是printABCUsingLock实例的printA方法 【实例::方法】 new Thread(printABCUsingLock::pringB).start(); new Thread(printABCUsingLock::pringC).start(); } }

方法二:使用 Lock / Condition + state实现:即 A打印后唤醒等待在conditionB上的B 线程-> B打印后唤醒等待在conditionC上的C线程->C打印后唤醒等待在conditionA上的A线程(和法一类似,只不过在获取锁后,又不该它打印时,它选择await()放弃锁进入等待队列直到被唤醒,而法一它放弃锁后会再去抢锁)
package testTools; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class PrintABCUsingLockCondition { private int times ; private int state; private Lock lock = new ReentrantLock(); private Condition conditionA = lock.newCondition(); private Condition conditionB = lock.newCondition(); private Condition conditionC = lock.newCondition(); public PrintABCUsingLockCondition(int times){ this.times = times; } public void printA(){ printABC("A",0,conditionA,conditionB); }public void printB(){ printABC("B",1,conditionB,conditionC); } public void printC(){ printABC("C",2,conditionC,conditionA); } private void printABC(String s, int targetState, Condition cur, Condition next) { for(int i=0; i

方法三:信号量 semaphore 实现【推荐】:即 A打印后释放信号量B-> B打印后释放信号量C->C 打印后释放信号量A
  • 直接使用 semaphore 中的 state 是否为1,为1时代表可执行。
release来释放资源前不一定要先通过acquire来获取一个资源,如果不断的release而不进行acquire将导致资源数虚增,所以一定得我们自己来保证使用的正确性。【所以你开始信号量为0,释放后将会变成1】
Seamphore无法去校验你获取许可和释放许可是否一一对应,因为获取和释放都是直接在AQS的state上操作的,所以操作一段时间后,连AQS自己都忘记最初的state值是啥了,所以当然无法在中途来校验获取和释放是否正确,即使知道state的初始值,也很难在交错的获取和释放许可的操作过程中做一致性检查。
  • 思路:我们把调用printA打印A的线程叫A线程,printB的叫做B线程,printC的叫做C线程
    • 刚开始只有信号量A有state=1,于是只有 A 线程 cur.acquire()能获得状态【因为只有它的cur是信号量A】,从而A线程打印了一个A,然后通过调用 next.release();让 信号量B 有了state = 1,此时信号量A的状态为0(因为已经被A线程拿走, A再想打印,要先等C打印后,释放信号量A的状态才可以),信号量C的状态也为0 ,所以从而只有线程B 能打印一次,B打印后又释放信号量C,从而C能打印
    • 即 A打印后释放信号量B-> B打印后释放信号量C->C 打印后释放信号量A
package test; import java.util.concurrent.Semaphore; public class Main { private static int times = 10; // 控制打印次数//当semaphoreA的state 有剩余(1),A 线程就能获得信号量,从而能打印 private static Semaphore semaphoreA = new Semaphore(1); private static Semaphore semaphoreB = new Semaphore(0); private static Semaphore semaphoreC = new Semaphore(0); static class MyThread extends Thread{ String thread; String s; Semaphore cur; Semaphore next; public MyThread(String thread,String s, Semaphore cur, Semaphore next){ this.thread = thread; this.s = s; this.cur= cur; this.next = next; } @Override public void run() { try { printABC(thread,s,cur,next); } catch (InterruptedException e) { e.printStackTrace(); } }private void printABC(String thread,String s, Semaphore cur, Semaphore next) throws InterruptedException { for(int i = 0; i


方法四: wait / notify 实现[使用 synchronized 保证线程安全,在调用wait(), notify()或notifyAll()的时候,必须先获得锁] wait(),notify()和notifyAll()都是java.lang.Object的方法:
这三个方法,都是Java语言提供的实现线程间阻塞(Blocking)和控制进程内调度(inter-process communication)的底层机制。在解释如何使用前,先说明一下两点:
1. 正如Java内任何对象都能成为锁(Lock)一样,任何对象也都能成为条件队列(Condition queue)。而这个对象里的wait(), notify()和notifyAll()则是这个条件队列的固有(intrinsic)的方法。
2.一个对象的固有锁和它的固有条件队列是相关的,为了调用对象X内条件队列的方法,你必须获得对象X的锁。这是因为等待状态条件的机制和保证状态连续性的机制是紧密的结合在一起的。
根据上述两点,在调用wait(), notify()或notifyAll()的时候,必须先获得锁,且状态变量须由该锁保护,而固有锁对象与固有条件队列对象又是同一个对象。也就是说,要在某个对象上执行wait,notify,先必须锁定该对象,而对应的状态变量也是由该对象锁保护的。
. 执行wait, notify时,不获得锁会如何?会抛出java.lang.IllegalMonitorStateException的异常。
public class PrintABCUsingWaitNotify { private int times; private int state; private Object objectA = new Object(); private Object objectB = new Object(); private Object objectC = new Object(); public PrintABCUsingWaitNotify(int times){ this.times = times; } public void printA(){ try { print("A", 0, objectA, objectB); } catch (InterruptedException e) { e.printStackTrace(); } } public void printB(){ try { print("B", 1, objectB, objectC); } catch (InterruptedException e) { e.printStackTrace(); } } public void printC(){ try { print("C", 2, objectC, objectA); } catch (InterruptedException e) { e.printStackTrace(); } } public void print(String name, int targetState, Object current, Object next) throws InterruptedException { for(int i = 0; i < times; ){ // 锁定当前线程 synchronized(current){//Object的wait之前一定要先获取该对象锁,否则会报异常 while(state % 3 != targetState){ current.wait(); } state++; i++; System.out.println("线程:" + name + ",第 " + i + " 打印"); synchronized(next){ // 通知下一个线程 next.notify(); } } } } public static void main(String[] args) { PrintABCUsingWaitNotify printABC = new PrintABCUsingWaitNotify(10); new Thread(printABC::printA).start(); new Thread(printABC::printB).start(); new Thread(printABC::printC).start(); } }


【【手撕代码】多个线程交替打印】


    推荐阅读