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

目录

方法一: 锁实现【推荐】:通过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 保证线程安全]

 


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:保证每次只有一个线程能够获取到资源。

package testTools;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PrintABCUsingLock {

    private static int times = 10;   // 控制打印次数
    private static int state = 0;   // 当前状态值:保证三个线程之间交替打印
    private static Lock lock = new ReentrantLock();   // 保证每次只有一个线程能够拿到资源

    static class ThreadA extends Thread{
        @Override
        public void run() {
            //我这个线程的第几次打印,没有获取锁或者获取了但没轮到我,
            // 我都会循环去获取锁,直到我打印了有10次
            for(int i = 0; i< times;){
                lock.lock();
                try {
                    if(state % 3 == 0){ //state从0 开始,只有轮到我的时候才开始打印
                        System.out.println("ThreadA : A 第" + i+ "次打印" );
                        state++; //打印后状态要加,比如由0 变成1 ,那么这时只有 B 能打印了,即轮到谁了
                        i++; //我的第 i 次打印;
                    }
                }finally {
                    lock.unlock();
                }
            }
        }
    }

    static class ThreadB extends Thread{
        @Override
        public void run() {
            //我这个线程的第几次打印,没有获取锁或者获取了但没轮到我,
            // 我都会循环去获取锁,直到我打印了有10次
            for(int i = 0; i< times;){
                lock.lock();
                try {
                    if(state % 3 == 1){ //state从0 开始,只有轮到我的时候才开始打印
                        System.out.println("ThreadB : B 第" + i+ "次打印" );
                        state++; //打印后状态要加,比如由0 变成1 ,那么这时只有 B 能打印了,即轮到谁了
                        i++; //我的第 i 次打印;
                    }
                }finally {
                    lock.unlock();
                }
            }
        }
    }

    static class ThreadC extends Thread{
        @Override
        public void run() {
            //我这个线程的第几次打印,没有获取锁或者获取了但没轮到我,
            // 我都会循环去获取锁,直到我打印了有10次
            for(int i = 0; i< times;){
                lock.lock();
                try {
                    if(state % 3 == 2){ //state从0 开始,只有轮到我的时候才开始打印
                        System.out.println("ThreadC : C 第" + i+ "次打印" );
                        state++; //打印后状态要加,比如由0 变成1 ,那么这时只有 B 能打印了,即轮到谁了
                        i++; //我的第 i 次打印;
                    }
                }finally {
                    lock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) {
        //这三个线程都会去抢锁,然后state%3 == 0 并且A线程抢到锁时,才会打印
        // (其他线程抢到锁因为不满足条件,所以会释放锁,但释放后又会继续枪锁)
        new ThreadA().start();
        new ThreadB().start();
        new ThreadC().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<times; ){
            lock.lock();  //每个线程每次打印都要先获得锁,然后再看它是否满足打印条件,即是否真的该它打印
            try {
                while (state%3 != targetState){ //说明不该它打印,于是它要让出锁,然后等待该它打印的时候被唤醒
                    cur.await();  //要用while,放止提前醒(maybe中断啥的导致醒了)
                }
                System.out.println("线程"+Thread.currentThread()+": "+ s + "第"+ i +"次打印");
                state++;
                i++;
                next.signal(); // 唤醒下一个让它打印
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        PrintABCUsingLockCondition printABC = new PrintABCUsingLockCondition(10);
        new Thread(printABC::printA).start();
        new Thread(printABC::printB).start();
        new Thread(printABC::printC).start();
    }
}

方法三:信号量 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
public class PrintABCUsingSemaphore {
    private int times ;
    //当semaphoreA的state 有剩余(1),A 线程就能获得信号量,从而能打印
    private Semaphore semaphoreA = new Semaphore(1);
    private Semaphore semaphoreB = new Semaphore(0);
    private Semaphore semaphoreC = new Semaphore(0);

    public PrintABCUsingSemaphore(int times){
        this.times = times;
    }
    public void printA(){
        try {
            printABC("A",semaphoreA,semaphoreB);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void printB(){
        try {
            printABC("B",semaphoreB,semaphoreC);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void printC(){
        try {
            printABC("C",semaphoreC,semaphoreA);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void printABC(String s, Semaphore cur, Semaphore next) throws InterruptedException {
        for(int i = 0; i<times;i++){
            cur.acquire();  //轮到某线程打印了,那么它的信号量cur肯定是1,【因为要么是刚开始,A就有1,要么是C线程打印完了,释放了A的信号量】
            System.out.println("线程"+Thread.currentThread()+": "+ s + "第"+ i +"次打印");
            next.release(); //此时打印完的线程占有了cur这个信号量仅有的状态,再释放下一个信号量,那么下一个线程就能打印了
        }
    }

    public static void main(String[] args) {
        PrintABCUsingSemaphore printABCUsingSemaphore = new PrintABCUsingSemaphore(10);
        new Thread(printABCUsingSemaphore::printA).start(); //此线程调用 printA方法打印 A
        new Thread(printABCUsingSemaphore::printB).start(); //此线程调用 printB方法打印 B
        new Thread(printABCUsingSemaphore::printC).start(); //此线程调用 printC方法打印 C
    }
}

 

方法四: 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();
    }
}

 

猜你喜欢

转载自blog.csdn.net/ZHAOJING1234567/article/details/89462442