Java并发-05-多线程并发设计模式-02

1-Guarded Suspension模式

1.1-介绍

Guarded Suspension,用在一个线程等待另一个线程的执行结果

  • 有一个结果需要从一个线程传递到另一个线程,让它们关联同一个 GuardedObject

  • 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)

  • JDK 中,join 的实现、Future 的实现,采用的就是此模式

1.2-demo体现

@Slf4j
public class GuardedObject {
    private Object response;
    private final Object lock =new Object();

    /**
     *
     * @param times 等待的毫秒数
     * @return
     */
    public Object get(Long times){
        synchronized (lock){
            long base=System.currentTimeMillis();
            long timePassed=0;//已经经历过的时间
            while(response==null){
                long waitTime=times-timePassed;
                if(waitTime<=0){
                    log.info("break....");
                    break;
                }
                try {
                    lock.wait(waitTime);
                } catch (InterruptedException e) {
                   log.error("InterruptedException={}",e);
                }
                timePassed=System.currentTimeMillis()-base;
            }
            return response;
        }
    }

    public void complete(Object obj){
        synchronized (lock){
            this.response=obj;
            lock.notifyAll();
        }
    }

}

@Slf4j
public class TestGuardedSuspension {
    public static void main(String[] args){
        GuardedObject object=new GuardedObject();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);//模拟耗时
                object.complete("hello,GuardedSuspension");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        Object resp = object.get(1000L);
        if(resp !=null){
            log.info("resp={}",resp);
        }else{
            log.info("resp is null");
        }

    }
}

所谓Guarded Suspension,直译过来就是“保护性地暂停”。Guarded Suspension模式本质上是一种等待唤醒机制的实现,只不过Guarded Suspension模式将其规范化了。

1.3-jdk源码thread.join()

jdk源码中thread.join()就使用了Guarded Suspension模式,看和1.2节代码是不是很相似...

2-Balking模式

2.1-介绍

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

Balking模式有一个非常典型的应用场景就是单次初始化,下面的示例代码是它的实现。这个实现方案中,我们将init()声明为一个同步方法,这样同一个时刻就只有一个线程能够执行init()方法;init()方法在第一次执行完时会将inited设置为true,这样后续执行init()方法的线程就不会再执行doInit()了。

2.2-demo体现

public class InitTest{
  volatile boolean inited = false;
  synchronized void init(){
    if(inited){
      return;
    }
    //省略doInit的实现
    doInit();
    inited=true;
  }
}

比如我们熟知的并发版本的单例模式就是采用了Balking模式;

public class SafeSingleton {
    private volatile static SafeSingleton singleton=null;
    private SafeSingleton(){}
    public static SafeSingleton getInstance(){
        if(singleton==null){
            synchronized (SafeSingleton.class){
                if(singleton==null){
                    singleton=new SafeSingleton();
                }
            }
        }
        return singleton;
    }
}

3-Thread-Per-Message模式

Thread-Per-Message模式,简言之就是为每个任务分配一个独立的线程。

Thread-Per-Message模式的一个最经典的应用场景是网络编程里服务端的实现,服务端为每个客户端请求创建一个独立的线程,当线程处理完请求后,自动销毁,这是一种最简单的并发处理网络请求的方法。比如网络编程中,主线程只接受连接,具体的读写业务逻辑交给其他线程处理,都是属于这种模式。

Thread-Per-Message模式在Java领域并不是那么知名,根本原因在于Java语言里的线程是一个重量级的对象,为每一个任务创建一个线程成本太高,尤其是在高并发领域,基本就不具备可行性。不过这个背景条件目前正在发生巨变,Java语言未来一定会提供轻量级线程,这样基于轻量级线程实现Thread-Per-Message模式就是一个非常靠谱的选择。

Java语言目前也已经意识到轻量级线程的重要性了,OpenJDK有个Loom项目,就是要解决Java语言的轻量级线程问题,在这个项目中,轻量级线程被叫做Fiber

4-WorkerThread模式

Thread-Per-Message模式,对应到现实世界,其实就是委托代办。这种分工模式如果用JavaThread实现,频繁地创建、销毁线程非常影响性能,同时无限制地创建线程还可能导致OOM。

Worker Thread模式能避免线程频繁创建、销毁的问题,而且能够限制线程的最大数量。Java语言里可以直接使用线程池来实现Worker Thread模式

5-两阶段终止模式

5.1-介绍

两阶段终止模式。顾名思义,就是将终止过程分成两个阶段,其中第一个阶段主要是线程T1向线程T2发送终止指令,而第二阶段则是线程T2响应终止指令

5.2-demo体现

如何优雅终止一个现场,使用标志位...

@Slf4j
public class TestInterrupt {
    static volatile boolean isStop = false;

    public static void main(String[] args) {
        Thread t1=new Thread(() -> {
            while (true) {

                if (Thread.currentThread().isInterrupted()) {//如果这个标志位被其他线程改为true了
                    log.info(Thread.currentThread().getName() + "\t线程中断,程序终止");
                    break;
                }

                if (isStop) {//如果这个标志位被其他线程改为true了
                    log.info(Thread.currentThread().getName() + "\tisStop被修改为true,程序终止");
                    break;
                }
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                   log.error("t1-InterruptedException,e={}",e);
                    Thread.currentThread().interrupt();
                }
                log.info("t1------TestInterrupt");//-----如果没停止,那就一直打印
            }
        }, "t1");
        t1.start();

        try {
            TimeUnit.MILLISECONDS.sleep(60);
        } catch (InterruptedException e) {
            log.error("main-InterruptedException,e={}",e);
        }

        Thread t2=new Thread(() -> {
            isStop = true;
            t1.interrupt();
        }, "t2");
        t2.start();
    }
}

控制台输出:

14:24:00.359 [t1] INFO com.ycmy2023.interrupt.TestInterrupt - t1------TestInterrupt

14:24:00.373 [t1] INFO com.ycmy2023.interrupt.TestInterrupt - t1------TestInterrupt

14:24:00.389 [t1] INFO com.ycmy2023.interrupt.TestInterrupt - t1------TestInterrupt

14:24:00.405 [t1] INFO com.ycmy2023.interrupt.TestInterrupt - t1------TestInterrupt

14:24:00.408 [t1] ERROR com.ycmy2023.interrupt.TestInterrupt - t1-InterruptedException,e={}

java.lang.InterruptedException: sleep interrupted

at java.lang.Thread.sleep(Native Method)

at java.lang.Thread.sleep(Thread.java:340)

at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)

at com.ycmy2023.interrupt.TestInterrupt.lambda$main$0(TestInterrupt.java:25)

at java.lang.Thread.run(Thread.java:748)

14:24:00.409 [t1] INFO com.ycmy2023.interrupt.TestInterrupt - t1------TestInterrupt

14:24:00.409 [t1] INFO com.ycmy2023.interrupt.TestInterrupt - t1线程中断,程序终止

5.3-jdk中使用

shutdown()和shutdownNow()

线程池中shutdown()和shutdownNow()方法你会发现,它们实质上使用的也是两阶段终止模式,只是终止指令的范围不同而已,前者只影响阻塞队列接收任务,后者范围扩大到线程池中所有的任务。

6-生产者-消费者模式

6.1-介绍

Java线程池本质上就是用生产者-消费者模式实现的,所以每当使用线程池的时候,其实就是在应用生产者-消费者模式。

生产者-消费者模式的核心是一个任务队列,生产者线程生产任务,并将任务添加到任务队列中,而消费者线程从任务队列中获取任务并执行。

解耦:在生产者-消费者模式中,生产者和消费者没有任何依赖关系,它们彼此之间的通信只能通过任务队列。

生产者-消费者模式还有一个重要的优点就是支持异步,并且能够平衡生产者和消费者的速度差异。

应用场景:Java线程池,MQ。

6.2-demo体现

@Slf4j
public class TestProCos {
    public static void main(String[] args) {
        ExecutorService consumer = Executors.newFixedThreadPool(1);
        ExecutorService producer = Executors.newFixedThreadPool(1);
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(2);
        producer.submit(() -> {
            try {
                log.info("生产...");
                Thread.sleep(1000);
                queue.put(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        consumer.submit(() -> {
            try {
                log.info("等待消费...");
                Integer result = queue.take();
                log.info("结果为:" + result);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

7-小结

Immutability模式Copy-on-Write模式线程本地存储模式本质上都是为了避免共享,只是实现手段不同而已。这3种设计模式的实现都很简单,但是实现过程中有些细节还是需要格外注意的。例如,使用Immutability模式需要注意对象属性的不可变性,使用Copy-on-Write模式需要注意性能问题,使用线程本地存储模式需要注意异步执行问题

Guarded Suspension模式Balking模式都可以简单地理解为“多线程版本的if”,但它们的区别在于前者会等待if条件变为真,而后者则不需要等待。

Thread-Per-Message模式Worker Thread模式生产者-消费者模式是三种最简单实用的多线程分工方法。Thread-Per-Message模式在实现的时候需要注意是否存在线程的频繁创建、销毁以及是否可能导致OOM。Worker Thread模式的实现,需要注意潜在的线程死锁问题

如何优雅地终止线程?两阶段终止模式是一种通用的解决方案。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值