模式之固定与交替顺序执行

固定顺序执行

wait && notify

基本思路:

  1. 线程t1循环等待线程t2执行条件结束
  2. 线程t2打印2, 然后唤醒wait的线程
package cn.knightzz.partten;

import lombok.extern.slf4j.Slf4j;

/**
 * @author 王天赐
 * @title: TestAsyncModePrint
 * @projectName hm-juc-codes
 * @description: 顺序控制之同步打印
 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
 * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
 * @create: 2022-07-24 13:38
 */
@SuppressWarnings("all")
@Slf4j(topic = "c.TestAsyncModePrint")
public class TestAsyncModePrint {

    // 基本思路:
    // 1. 线程t1循环等待线程t2执行条件结束
    // 2. 线程t2打印2, 然后唤醒wait的线程

    private static Object lock = new Object();
    private static boolean isFinish = true;

    public static void main(String[] args) {


        // 固定顺序, 必须先打印2再打印1

        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                while (!isFinish) {
                    try {
                        // wait 的时候线程会释放锁
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

                log.debug("print 1");
            }
        }, "t1");

        t1.start();

        Thread t2 = new Thread(() -> {
            log.debug("print 2");
            synchronized (lock) {
                // 修改执行标记, 唤醒所有的线程
                isFinish = true;
                // 唤醒其他线程, 让其他县城进入 EntryList , 等待锁
                // t2 执行完也会释放锁
                lock.notifyAll();
            }
        }, "t2");
        t2.start();
    }
}

img

Park && unPark

可以看到,实现上很麻烦:

  • 首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了『运行标记』来判断该不该 wait
  • 第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决 此问题
  • 最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为『同步对象』上的等待线程可能不止一个
package cn.knightzz.partten;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

/**
 * @author 王天赐
 * @title: TestAsyncModePrint
 * @projectName hm-juc-codes
 * @description: 顺序控制之同步打印
 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
 * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
 * @create: 2022-07-24 13:38
 */
@SuppressWarnings("all")
@Slf4j(topic = "c.TestAsyncModePrintPark")
public class TestAsyncModePrintPark {

    // 基本思路:
    // 1. 线程t1循环等待线程t2执行条件结束
    // 2. 线程t2打印2, 然后唤醒wait的线程

    private static Object lock = new Object();

    public static void main(String[] args) {


        // 固定顺序, 必须先打印2再打印1

        Thread t1 = new Thread(() -> {
            // 等待许可证
            LockSupport.park();
            log.debug("print 1");
        }, "t1");

        t1.start();

        Thread t2 = new Thread(() -> {
            log.debug("print 2");
            // 给t1发放许可证 ,(多次连续调用 unpark 只会发放一个『许可』)
            LockSupport.unpark(t1);
        }, "t2");
        t2.start();
    }
}

img

park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和恢复, 不需要同步对象运行标记

交替执行

wait && notify

基本思路
  • 需要定义一个类用于存储 flag 和 打印的次数
  • 假如我们要打印 a b c , 那么 flag 就对应这 1 2 3
  • 循环判断 flag 是否满足条件, 如果不满足就一直等待, 满足条件后就打印
  • 然后设置 flag = nextFlag , 然后唤醒其他线程
  • 所有的线程共享同一个 类对象
代码实现
package cn.knightzz.partten;

import lombok.extern.slf4j.Slf4j;

/**
 * @author 王天赐
 * @title: TestAlterPrintWait
 * @projectName hm-juc-codes
 * @description: 使用wait&&notify交替打印
 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
 * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
 * @create: 2022-07-24 15:18
 */
@SuppressWarnings("all")
@Slf4j(topic = "c.TestAsyncModePrintPark")
public class TestAlterPrintWait {

    // 基本思路 :
    // 1. 设置一个整数标记位, 假如线程是3个 t1 => 1, t2 ==> 2 , t3 ==> 3
    // 满足条件就打印 , 不满足就循环
    // 2. 设置nextFlag, 用于设置下一个打印的标记位


    public static void main(String[] args) {

        WaitNotify waitNotify = new WaitNotify(1, 5);
        new Thread(() -> {
            waitNotify.print(1, 2, "a");
        }, "t1").start();
        new Thread(() -> {
            waitNotify.print(2, 3, "b");
        }, "t2").start();
        new Thread(() -> {
            waitNotify.print(3, 1, "c");
        }, "t3").start();
    }
}

@SuppressWarnings("all")
class WaitNotify {

    // 等待标记位
    private int flag;

    // 设置循环次数
    private int loopNumber;

    public WaitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }

    /**
     * 打印信息 , 要求实现 abcabcabc 这样的效果
     *
     * @param waitFlag 等待条件标记位
     * @param nextFlag 下一个打印的标记位
     * @param str      打印的信息
     */
    public void print(int waitFlag, int nextFlag, String str) {

        for (int i = 0; i < loopNumber; i++) {
            synchronized (this) {
                // 判断当前标记位是否满足条件
                while (waitFlag != flag) {
                    // 不满足条件就一直等待
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                // 满足条件就打印信息
                System.out.println("str = " + str);
                // 设置下一个标记位
                this.flag = nextFlag;
                // 唤醒其他的线程
                this.notifyAll();
            }
        }
    }
}

lock条件变量

基本思路
  • 定义 waitSingal 类继承 ReentrantLock
  • 通过定义不同的 Condition 对象, 让不同的线程在对应的 WaitSet 里等待.
  • 当前线程执行完以后, 释放锁, 然后唤醒下一个线程
代码实现
package cn.knightzz.partten;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 王天赐
 * @title: TestAlterPrintLock
 * @projectName hm-juc-codes
 * @description: 使用Lock实现交替打印
 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
 * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
 * @create: 2022-07-25 07:32
 */
@SuppressWarnings("all")
@Slf4j(topic = "c.TestAlterPrintLock")
public class TestAlterPrintLock {

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

        AwaitSignal awaitSignal = new AwaitSignal(5);

        // 创建不同的等待队列
        Condition conditionA = awaitSignal.newCondition();
        Condition conditionB = awaitSignal.newCondition();
        Condition conditionC = awaitSignal.newCondition();

        new Thread(() -> {
            awaitSignal.print("a", conditionA, conditionB);
        }, "t1").start();

        new Thread(() -> {
            awaitSignal.print("b", conditionB, conditionC);
        }, "t2").start();

        new Thread(() -> {
            awaitSignal.print("c", conditionC, conditionA);
        }, "t3").start();


        // 睡眠 1s
        TimeUnit.SECONDS.sleep(1);
        // 主线程先获取锁, 即使其他线程先拿到锁的话, 也会直接await(), 等待时会进入对应的Condition, 然后释放锁
        awaitSignal.lock();
        log.debug("==============================================");
        log.debug("主线程先获取锁, 即使其他线程先拿到锁的话, 也会直接await(), 等待时会进入对应的Condition, 然后释放锁");
        log.debug("==============================================");
        try {
            // 唤醒条件A线程
            log.debug("开始 ... ");
            conditionA.signal();
        }finally {
            awaitSignal.unlock();
        }

    }

}

@SuppressWarnings("all")
@Slf4j(topic = "c.AwaitSignal")
class AwaitSignal extends ReentrantLock {

    private int loopNumber;

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

    public void print(String info, Condition current, Condition next) {

        for (int i = 0; i < loopNumber; i++) {
            // 获取锁, 锁的作用是为了保证原子性, 锁获取的周期内, 代码可以完整的执行 , 而不会进行线程上下文的切换
            this.lock();
            log.debug("成功获取锁!");
            try {
                // 当前线程等待
                log.debug("当前线程等待!");
                current.await();
                log.debug("等待结束打印 info!");
                // 等待结束打印 info
                log.debug(" info = {}", info);
                // 唤醒下一个Condition的线程
                log.debug("唤醒下一个Condition的线程!");
                next.signal();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                // 释放锁
                this.unlock();
            }
        }
    }
}

img

park && unpark

基本思路
  • 思路很简单, 定义一个 SyncPark 类 , 定义一个 Thread 数组对象threads, 然后用于存储线程
  • 获取下一个线程的时候, 就遍历 threads , 然后使用 LockSupport.unpark(next);
代码实现
package cn.knightzz.partten;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

/**
 * @author 王天赐
 * @title: TestAlterPrintPark
 * @projectName hm-juc-codes
 * @description: 使用Park&&UnPark交替打印
 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
 * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
 * @create: 2022-07-25 09:05
 */
@SuppressWarnings("all")
@Slf4j(topic = "c.TestAlterPrintPark")
public class TestAlterPrintPark {


    public static void main(String[] args) {


        SyncPark syncPark = new SyncPark(5);

        Thread t1 = new Thread(() -> {
            syncPark.print("a");
        }, "t1");

        Thread t2 = new Thread(() -> {
            syncPark.print("b");
        }, "t2");

        Thread t3 = new Thread(() -> {
            syncPark.print("c");
        }, "t3");

        syncPark.setThreads(t1, t2, t3);
        syncPark.start();
    }

}

@Slf4j(topic = "c.SyncPark")
@SuppressWarnings("all")
class SyncPark {

    private int loopNumber;
    private Thread[] threads;

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

    public void setThreads(Thread... threads) {
        this.threads = threads;
    }

    public void print(String info) {

        for (int i = 0; i < loopNumber; i++) {
            // 获取锁
            LockSupport.park();
            // 打印
            log.debug("info = {} ", info);
            // 释放锁
            Thread next = nextThread();
            LockSupport.unpark(next);
        }
    }

    public Thread nextThread() {

        // 获取当前线程
        Thread current = Thread.currentThread();
        int index = 0;

        // 遍历线程数组, 找到当前线程
        for (int i = 0; i < threads.length; i++) {
            if (threads[i] == current) {
                index = i;
                break;
            }
        }

        if (index < threads.length - 1) {
            return threads[index + 1];
        } else {
            return threads[0];
        }
    }

    public void start() {

        for (Thread thread : threads) {
            thread.start();
        }

        // 释放凭证
        LockSupport.unpark(threads[0]);
    }
}

img

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兀坐晴窗独饮茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值