两阶段终止模式和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输出一次记录,不会再像之前一样,每秒打印多次记录。

目录
相关文章
|
9月前
|
消息中间件 存储 Kafka
Flink---11、状态管理(按键分区状态(值状态、列表状态、Map状态、归约状态、聚合状态)算子状态(列表状态、广播状态))
Flink---11、状态管理(按键分区状态(值状态、列表状态、Map状态、归约状态、聚合状态)算子状态(列表状态、广播状态))
|
11月前
|
Java 调度
线程包括哪些状态,状态之间是如何变化的
线程包括哪些状态,状态之间是如何变化的
60 0
|
2月前
|
存储 消息中间件 分布式计算
流计算中的状态管理是什么?请解释其作用和常用方法。
流计算中的状态管理是什么?请解释其作用和常用方法。
34 0
|
12月前
|
存储 算法 NoSQL
分布式事务两阶段提交和三阶段提交有什么区别?
分布式事务两阶段提交和三阶段提交有什么区别?
177 0
分布式事务两阶段提交和三阶段提交有什么区别?
|
设计模式 监控 Java
线程之两阶段终止模式(Two-Phase Termination Patter)模式
两阶段终止模式(Two-Phase Termination Pattern)是一种软件设计模式,用于管理线程或进程的生命周期。它包括两个阶段:第一阶段是准备阶段,该阶段用于准备线程或进程的停止;第二阶段是停止阶段,该阶段用于执行实际的停止操作。这种模式的主要目的是确保线程或进程在停止之前完成必要的清理和资源释放操作,以避免出现资源泄漏和数据损坏等问题。两阶段终止模式是一种常见的并发编程模式,广泛应用于Java和其他编程语言中。
155 0
线程之两阶段终止模式(Two-Phase Termination Patter)模式
|
12月前
|
编译器 程序员 C语言
编译时间和运行态时间交换的优缺点
编译时间和运行态时间交换的优缺点
70 0
|
消息中间件 Java
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式(一)
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式(一)
|
缓存 监控 安全
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式(二)
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式
《JUC并发编程 - 模式篇》保护性暂停模式 | 顺序控制模式 | 生产者消费者模式 | 两阶段终止模式 | Balking模式 | 享元模式(二)
|
Java 程序员 调度
线程的创建方式,状态周期管理
进程是计算机中的程序,关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
109 0
线程的创建方式,状态周期管理
|
监控
【多线程:两阶段终止模式】volatile版本
【多线程:两阶段终止模式】volatile版本
93 0