上篇博客中用volatitle实现的两阶段之终止模式,若在测试方法中执行两次monitorThread.start()方法,就会导致监控线程成对执行。有时候我们我们需要的某个线程已经被执行之后还有线程执行它就不再执行。
如何保证某个方法只被执行一次,下次其他线程再执行就直接返回,不要执行了。
在之前的方法中再添加一个是否已经被执行过了的标记,一开始是flase,表示还没有被执行,当它被执行后,将该标记设置为true:
private boolean stop=false;
if(starting){
//表示已经执行过了,不需要再创建监控线程继续往下执行了
return ;
}
//若是第一次运行,starting的值为假,它会往下执行,创建监控线程,执行监控
//若是第一次运行,还应该设置starting为true,让它下次被执行到if时直接返回
starting=true;
@Slf4j(topic = “c.TwoPharse1”)
public class TwoPharse1 {
public static void main(String[] args) throws InterruptedException {
//测试方法
TwoPharse1 twp=new TwoPharse1();
twp.start();
TimeUnit.SECONDS.sleep(5);
log.debug(“停止监控”);
twp.stop();
}
private Thread monitorThread;
private boolean stop=false;
//判断是否执行过该方法
private boolean starting=false;
public void start(){
**if(starting){
//表示已经执行过了,不需要再创建监控线程继续往下执行了
return ;
}
//若是第一次运行,starting的值为假,它会往下执行,创建监控线程,执行监控
//若是第一次运行,还应该设置starting为true,让它下次被执行到if时直接返回
starting=true;**
monitorThread=new Thread(()->{
while(true){
Thread current=Thread.currentThread();
//是否被打断
if(stop){
//被中断了
log.debug("料理后事");
break;
}
//没有被中断
try {
TimeUnit.SECONDS.sleep(1);
log.debug("执行监控记录");
} catch (InterruptedException e) {
}
}
},"monitor");
monitorThread.start();
}
//在主线程中执行中断----停止监控线程
public void stop(){
stop=true;
monitorThread.interrupt();
}
}
现在的逻辑在多线程下会有问题:
比如,有两个线程同时调用了monitorThread.start(),第一个线程进去后判断starting是false,单它还没有往下执行到starting为true,第二个线程又来了,它去判断starting的值依然为false,这时两个线程都会去修改starting的值,去创建监控线程。
所以需要用synchronized去修饰上述判断starting和修改starting的这段代码:
synchronized (this){
if(starting){
//表示已经执行过了,不需要再创建监控线程继续往下执行了
return ;
}
//若是第一次运行,starting的值为假,它会往下执行,创建监控线程,执行监控
//若是第一次运行,还应该设置starting为true,让它下次被执行到if时直接返回
starting=true;
monitorThread=new Thread(()->{
while(true){
Thread current=Thread.currentThread();
//是否被打断
if(stop){
//被中断了
log.debug("料理后事");
break;
}
//没有被中断
try {
TimeUnit.SECONDS.sleep(1);
log.debug("执行监控记录");
} catch (InterruptedException e) {
}
}
},"monitor");
monitorThread.start();
}
@Slf4j(topic = “c.TwoPharse1”)
public class TwoPharse1 {
public static void main(String[] args) throws InterruptedException {
//测试方法
TwoPharse1 twp=new TwoPharse1();
twp.start();
twp.start();
twp.start();
TimeUnit
.SECONDS.sleep(5);
log.debug("停止监控");
twp.stop();
}
private Thread monitorThread;
private volatile boolean stop=false;
//判断是否执行过该方法
private boolean starting=false;
public void start(){
synchronized (this){
if(starting){
//表示已经执行过了,不需要再创建监控线程继续往下执行了
return ;
}
//若是第一次运行,starting的值为假,它会往下执行,创建监控线程,执行监控
//若是第一次运行,还应该设置starting为true,让它下次被执行到if时直接返回
starting=true;
monitorThread=new Thread(()->{
while(true){
Thread current=Thread.currentThread();
//是否被打断
if(stop){
//被中断了
log.debug("料理后事");
break;
}
//没有被中断
try {
TimeUnit.SECONDS.sleep(1);
log.debug("执行监控记录");
} catch (InterruptedException e) {
}
}
},"monitor");
monitorThread.start();
}
}
//在主线程中执行中断----停止监控线程
public void stop(){
stop=true;
monitorThread.interrupt();
}
}
加上锁后,即使在测试方法中同时启动多个监控线程也只会创建一个监控线程,测试结果:
22:34:57.879 [monitor] DEBUG c.TwoPharse1 - 执行监控记录
22:34:58.884 [monitor] DEBUG c.TwoPharse1 - 执行监控记录
22:34:59.886 [monitor] DEBUG c.TwoPharse1 - 执行监控记录
22:35:00.888 [monitor] DEBUG c.TwoPharse1 - 执行监控记录
22:35:01.876 [main] DEBUG c.TwoPharse1 - 停止监控
22:35:01.877 [monitor] DEBUG c.TwoPharse1 - 料理后事
因为synchronized同步代码块中代码越多,相当于上锁时间越长,并发度也就越低,因此尽量将在该代码块中放入较少的代码。
改进:将监控线程的创建和启动放在同步代码块之外,只保护starting变量的读和写
if(starting){
//表示已经执行过了,不需要再创建监控线程继续往下执行了
return ;
}
//若是第一次运行,starting的值为假,它会往下执行,创建监控线程,执行监控
//若是第一次运行,还应该设置starting为true,让它下次被执行到if时直接返回
starting=true;
}
monitorThread=new Thread(()->{
while(true){
Thread current=Thread.currentThread();
//是否被打断
if(stop){
//被中断了
log.debug("料理后事");
break;
}
//没有被中断
try {
TimeUnit.SECONDS.sleep(1);
log.debug("执行监控记录");
} catch (InterruptedException e) {
}
}
},"monitor");
monitorThread.start();
}
Balking模式的定义:
Balking(犹豫)模式用在一个线程发现另一个线程或本线程已经做了一某一件相同的事,那么笨线程就无需再做了,直接结束返回。