两阶段终止模式和Balking模式

简介: 两阶段终止模式和Balking模式

一、volatile改进两阶段终止模式

1.1 示例

import lombok.extern.slf4j.Slf4j;
/**
 * Created by xiaowei
 * Date 2022/10/29
 * Description volatile实现两阶段终止
 */
@Slf4j(topic = "c.Test09")
public class Test09 {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        Thread.sleep(3500);
        log.debug("停止监控");
        tpt.stop();
    }
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
    // 监控线程
    private Thread monitorThread;
    // 停止标记
    private volatile boolean stop = false;
    // 启动监控线程
    public void start() {
        monitorThread = new Thread(() -> {
            while (true) {
                //是否被打断
                if(stop){
                    log.debug("料理后事");
                    break;
                }
                try {
                    Thread.sleep(1000);
                    log.debug("执行监控记录");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1");
        monitorThread.start();
    }
    //停止监控线程
    public void stop(){
        stop = true;
        monitorThread.interrupt();
    }
}

运行结果

09:55:59.124 c.TwoPhaseTermination [t1] - 执行监控记录
09:56:00.131 c.TwoPhaseTermination [t1] - 执行监控记录
09:56:01.132 c.TwoPhaseTermination [t1] - 执行监控记录
09:56:01.623 c.Test09 [main] - 停止监控
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.lilinchao.concurrent.demo_03.TwoPhaseTermination.lambda$start$0(Test09.java:40)
    at java.lang.Thread.run(Thread.java:748)
09:56:01.624 c.TwoPhaseTermination [t1] - 料理后事

代码分析

  • TwoPhaseTermination类中调用start()方法,启动一个t1线程。
  • t1线程中while(true)方法循环执行监控记录,使得t1线程无法结束。
  • stop方法初始值为false,t1线程刚启动时,不会进入if(stop)条件判断中执行break;退出循环。
  • stop()方法中将成员变量stop改为true,同时调用monitorThread.interrupt();方法,为了打断正在睡眠中的t1线程,使其停止睡眠立即向下执行。
  • 要想使t1线程能够读取到成员变量stop在主线程中的更改,加上关键字volatile

执行过程分析

  • 主线程中调用TwoPhaseTermination类的start()方法,开启t1线程;
  • t1线程中每睡眠1s控制台输出一次“执行监控记录”;
  • 主线程在3.5秒后调用stop()方法,将stop设置成true,准备停止t1线程;
  • 此时t1线程已经执行完第三轮循环,正处于第四轮循环的睡眠状态,但是被monitorThread.interrupt();方法打断睡眠,抛出一个打断异常后,开始第五轮循环;
  • 此时stop成员变量为true,进入if判断条件,料理后事后退出循环;
  • t1线程运行结束,退出。

1.2 该方式存在的缺陷

当我们多次调用start()方法时,会创建多个t1线程,整个线程都在重复执行,消耗资源且没有意义。

public static void main(String[] args) throws InterruptedException {
    TwoPhaseTermination tpt = new TwoPhaseTermination();
    tpt.start();
    tpt.start();
    Thread.sleep(3500);
    log.debug("停止监控");
    tpt.stop();
}

运行结果

10:04:33.048 c.TwoPhaseTermination [t1] - 执行监控记录
10:04:33.049 c.TwoPhaseTermination [t1] - 执行监控记录
10:04:34.052 c.TwoPhaseTermination [t1] - 执行监控记录
10:04:34.052 c.TwoPhaseTermination [t1] - 执行监控记录
10:04:35.052 c.TwoPhaseTermination [t1] - 执行监控记录
10:04:35.052 c.TwoPhaseTermination [t1] - 执行监控记录
10:04:35.547 c.Test09 [main] - 停止监控
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.lilinchao.concurrent.demo_03.TwoPhaseTermination.lambda$start$0(Test09.java:41)
    at java.lang.Thread.run(Thread.java:748)
10:04:35.549 c.TwoPhaseTermination [t1] - 料理后事
10:04:36.053 c.TwoPhaseTermination [t1] - 执行监控记录
10:04:36.053 c.TwoPhaseTermination [t1] - 料理后事

问题?

如何保证某个方法被多次调用时,只会执行一次,下次在执行直接返回,不再继续向下执行。

可以通过Balking模式来实现。

二、Balking模式

2.1 定义

Balking (犹豫)模式用在一个线程发现另一个线程或本线程已经做了某一件相同的事,那么本线程就无需再做了,直接结束返回。

2.2 实现

Balking模式就是加入一个全局变量starting,通过starting的状态来判断该线程是否被调用过

private volatile boolean starting;

在调用方法中加入条件判断

if (starting) {
    return;
}
starting = true;
  • 当方法第一次被调用,starting变量为false,不进入条件判断语句,继续向下执行,这时将starting状态改为true;
  • 当方法已经被调用过时,此时状态值starting被改为true,进入到条件判断当中,执行return,退出该方法。

示例

public class MonitorService {
    // 用来表示是否已经有线程已经在执行启动了
    private volatile boolean starting;
    public void start() {
        log.info("尝试启动监控线程...");
        synchronized (this) {
            if (starting) {
                return;
            }
            starting = true;
        }
        // 真正启动监控线程...
    }
}

思考:为什么需要在Balking模式代码中加上synchronized锁?

if (starting) {
    return;
}
starting = true;

因为上方几段代码即涉及到了读,也涉及到了写,对于多行语句,在多线程情况下,保证的就不仅仅是可见性,必须得保证代码的原子性。所以,上方几段代码必须放在synchronized代码块中。

三、Balking模式应用

对上方volatile实现两阶段终止模式代码通过Balking模式进行改进

import lombok.extern.slf4j.Slf4j;
/**
 * Created by xiaowei
 * Date 2022/10/29
 * Description Balking模式应用
 */
@Slf4j(topic = "c.Test09")
public class Test09 {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        tpt.start();
        tpt.start();
        /*Thread.sleep(3500);
        log.debug("停止监控");
        tpt.stop();*/
    }
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
    // 监控线程
    private Thread monitorThread;
    // 停止标记
    private volatile boolean stop = false;
    // 判断是否执行过 start 方法
    private boolean starting = false;
    // 启动监控线程
    public void start() {
        synchronized (this){
            if (starting) {
                return;
            }
            starting = true;
        }
        monitorThread = new Thread(() -> {
            while (true) {
                //是否被打断
                if(stop){
                    log.debug("料理后事");
                    break;
                }
                try {
                    Thread.sleep(1000);
                    log.debug("执行监控记录");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1");
        monitorThread.start();
    }
    //停止监控线程
    public void stop(){
        stop = true;
        monitorThread.interrupt();
    }
}

运行结果

10:44:22.543 c.TwoPhaseTermination [t1] - 执行监控记录
10:44:23.546 c.TwoPhaseTermination [t1] - 执行监控记录
10:44:24.547 c.TwoPhaseTermination [t1] - 执行监控记录
10:44:25.547 c.TwoPhaseTermination [t1] - 执行监控记录
10:44:26.561 c.TwoPhaseTermination [t1] - 执行监控记录
10:44:27.561 c.TwoPhaseTermination [t1] - 执行监控记录
10:44:28.562 c.TwoPhaseTermination [t1] - 执行监控记录
10:44:29.562 c.TwoPhaseTermination [t1] - 执行监控记录
10:44:30.563 c.TwoPhaseTermination [t1] - 执行监控记录

从结果可以看出,每隔1s输出一次记录,不会再像之前一样,每秒打印多次记录。

目录
相关文章
|
4月前
|
算法
两阶段提交
【8月更文挑战第11天】
41 1
|
消息中间件 Java
同步模式之保护性暂停
同步模式之保护性暂停
|
存储 算法 NoSQL
分布式事务两阶段提交和三阶段提交有什么区别?
分布式事务两阶段提交和三阶段提交有什么区别?
303 0
分布式事务两阶段提交和三阶段提交有什么区别?
|
设计模式 监控 Java
线程之两阶段终止模式(Two-Phase Termination Patter)模式
两阶段终止模式(Two-Phase Termination Pattern)是一种软件设计模式,用于管理线程或进程的生命周期。它包括两个阶段:第一阶段是准备阶段,该阶段用于准备线程或进程的停止;第二阶段是停止阶段,该阶段用于执行实际的停止操作。这种模式的主要目的是确保线程或进程在停止之前完成必要的清理和资源释放操作,以避免出现资源泄漏和数据损坏等问题。两阶段终止模式是一种常见的并发编程模式,广泛应用于Java和其他编程语言中。
199 0
线程之两阶段终止模式(Two-Phase Termination Patter)模式
|
存储 分布式计算 安全
「分布式架构」最终一致性:暗示的切换队列
「分布式架构」最终一致性:暗示的切换队列
|
消息中间件 Java
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式(一)
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式(一)
|
缓存 监控 安全
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式(二)
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式(二)
|
存储 Java 编译器
JVM特点,基础结构与执行周期
虚拟机(Virtual Machine)指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。
181 0
JVM特点,基础结构与执行周期
有一台机器,并且给你这台机器的工作表,工作表上有n个任务,机器在ti时间执行第i个任务,1秒即可完成1个任务。 有m个询问,每个询问有一个数字q,表示如果在q时间有一个工作表之外的任务请求,请计算何时这个任务才能被执行。 机器总是按照工作表执行,当机器空闲时立即执行工作表之外的任务请求。
Input 输入的第一行包含一个整数T, 表示一共有T组测试数据。 对于每组测试数据: 第一行是两个数字n, m,表示工作表里面有n个任务, 有m个询问; 第二行是n个不同的数字t1, t2, t3....tn,表示机器在ti时间执行第i个任务。 接下来m行,每一行有一个数字q,表示在q时间有一个工作表之外的任务请求。 特别提醒:m个询问之间是无关的。 [Technical Specification] 1. T <= 50 2. 1 <= n, m <= 10^5 3. 1 <= ti <= 2*10^5, 1 <= i <= n 4. 1 <= q <= 2*10^5 Ou
154 0
|
监控
【多线程:两阶段终止模式】volatile版本
【多线程:两阶段终止模式】volatile版本
114 0