多线程和并发编程(6)—并发编程的设计模式

简介: 多线程和并发编程(6)—并发编程的设计模式

优雅终止

如何优雅终止线程?

中断线程的思路是使用两阶段法:第一阶段发生中断请求,第二阶段根据中断标识结束线程;

public class Test1 {
   
   
    private volatile static boolean interrupted = false;
    public static void main(String[] args) throws InterruptedException {
   
   
        Thread thread = new Thread(new Runnable() {
   
   
            @Override
            public void run() {
   
   
                while (!interrupted) {
   
   
                    try {
   
   
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName() + " waiting");
                    } catch (InterruptedException e) {
   
   
                        e.printStackTrace();
                        interrupted = true;
                    }
//                    System.out.println(Thread.currentThread().getName());
                }
            }
        });
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

使用两阶段终止线程可以使用 thread.interrupt()方法将运行中线程置为中断状态,或者让等待状态的线程抛出InterruptedException异常,再通过判断中断状态来实现线程退出。

如何优雅终止线程池?

停止线程池的方法有3中,包括使用shutdown()方法、使用shutdownNow()方法和使用JVM停服钩子函数。

  1. shutdown()方法会停止线程池接受新的任务,并等待线程池中的所有任务执行完毕,然后关闭线程池。在调用shutdown()方法后,线程池不再接受新的任务,但是会将任务队列中的任务继续执行直到队列为空。如果线程池中的任务正在执行,但是还没有执行完毕,线程池会等待所有任务执行完毕后再关闭线程池。
  2. shutdownNow()方法会停止线程池接受新的任务,并尝试中断正在执行任务的线程,然后关闭线程池。在调用shutdownNow()方法后,线程池不再接受新的任务,同时会中断正在执行任务的线程并返回一个未执行的任务列表。该方法会调用每个任务的interrupt()方法尝试中断任务执行的线程,但是并不能保证线程一定会被中断,因为线程可以选择忽略中断请求。
  3. 使用JVM提供的停服钩子函数来实现优雅停机,当停服时就会执行addShutdownHook()方法中的线程逻辑。
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
   
   
            @Override
            public void run() {
   
   
                try {
   
   
                    executor.awaitTermination(5,TimeUnit.MINUTES);
                } catch (InterruptedException e) {
   
   
                    e.printStackTrace();
                }
                System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
            }
        }));

总的来说,使用shutdown()方法停止线程池会等线程池中已经执行的任务完成后再销毁线程池,shutdownNow()方法会调用运行中线程的interrupt()方法尝试中断任务,但是否能真的停止无法保障,使用JVM停服钩子函数可以使用awaitTermination(5,TimeUnit.MINUTES)方法,等待固定时间让线程执行完,过了时间后再销毁。

避免共享

出现线程并发问题的条件是多个线程同时存在读写操作,所以一种思路是避免存在同时写的操作,甚至不能对共享变量进行写操作。

不可变模式Immutable

声明一个变量,让其不能进行写的操作,只能进行读的操作。这可以理解为一种设计模式:不可变模式。在Java中的实现可以是声明一个变量为final,这个变量赋值后就不能再被修改。可以使用类似guava中的ImmutableCollection的集合类型来实现。

image-20230926002124167

写时复制 CopyOnWrite

在Java中,CopyOnWriteArrayList 和 CopyOnWriteArraySet 这两个 Copy-on-Write 容器,它们背后的设计思想就是 Copy-on-Write;通过 Copy-on-Write 这两个容器实现的读操作是无锁的,由于无锁,所以将读操作的性能发挥到了极致。

Copy-on-Write模式适合读多写少的场景,他的实现思路是在需要进行写操作时候,会复制一个副本,在副本中进行写的操作,在写完之后再合并到原来的变量中。该种设计模式的问题在于每次都需要复制到新的内存中,所以会比较消耗内存。

线程本地变量ThreadLocal

线程本地存储模式用于解决多线程环境下的数据共享和数据隔离问题。该模式的基本思想是为每个线程创建独立的存储空间,用于存储线程私有的数据。通过这种方式,可以保证线程之间的数据隔离和互不干扰。在 Java 标准类库中,ThreadLocal 类实现了该模式。

线程本地存储模式本质上是一种避免共享的方案,由于没有共享,所以自然也就没有并发问题。如果你需要在并发场景中使用一个线程不安全的工具类,最简单的方案就是避免共享。避免共享有两种方案,一种方案是将这个工具类作为局部变量使用,另外一种方案就是线程本地存储模式。这两种方案,局部变量方案的缺点是在高并发场景下会频繁创建对象,而线程本地存储方案,每个线程只需要创建一个工具类的实例,所以不存在频繁创建对象的问题。

多线程版本的if模式

守护阻塞模式Guarded Suspension

Guarded Suspension 模式是通过让线程等待来保护实例的安全性,即守护-挂起模式。在多线程开发中,常常为了提高应用程序的并发性,会将一个任务分解为多个子任务交给多个线程并行执行,而多个线程之间相互协作时,仍然会存在一个线程需要等待另外的线程完成后继续下一步操作。而Guarded Suspension模式可以帮助我们解决上述的等待问题。

在Java中的实现包括:

  1. sychronized+wait/notify/notifyAll
  2. reentrantLock+Condition(await/singal/singalAll)
  3. cas+park/unpark

守护中断模式Balking

Balking是“退缩不前”的意思。如果现在不适合执行这个操作,或者没必要执行这个操作,就停止处理,直接返回。当流程的执行顺序依赖于某个共享变量的场景,可以归纳为多线程if模式。Balking 模式常用于一个线程发现另一个线程已经做了某一件相同的事,那么本线程就无需再做了,直接结束返回。

Balking模式和Guarded Suspension模式一样,存在守护条件,如果守护条件不满足,则中断处理;这与Guarded Suspension模式不同,Guarded Suspension模式在守护条件不满足的时候会一直等待至可以运行。

常见的使用场景有:

  1. 单例的DCL模式;
  2. 组件服务的初始化操作;

多线程分工模式

消息单线程模式Thread-Per-Message

为每一个处理任务分配一个线程。Thread-Per-Message 模式作为一种最简单的分工方案,Java 中使用会存在性能缺陷。在 Java 中的线程是一个重量级的对象,创建成本很高,一方面创建线程比较耗时,另一方面线程占用的内存也比较大。所以为每个请求创建一个新的线程并不适合高并发场景。为了解决这个缺点,Java 并发包里提供了线程池等工具类。

工作线程模式Worker Thread

要想有效避免线程的频繁创建、销毁以及 OOM 问题,就不得不提 Java 领域使用最多的 Worker Thread 模式。Worker Thread 模式可以类比现实世界里车间的工作模式:车间里的工人,有活儿了,大家一起干,没活儿了就聊聊天等着。Worker Thread 模式中 Worker Thread 对应到现实世界里,其实指的就是车间里的工人

Worker Thread 模式能避免线程频繁创建、销毁的问题,而且能够限制线程的最大数量。Java 语言里可以直接使用线程池来实现 Worker Thread 模式,线程池是一个非常基础和优秀的工具类,甚至有些大厂的编码规范都不允许用 new Thread() 来创建线程,必须使用线程池。

异步模式

对于共享资源的操作分为生产和消费两端,可以采用生产者-消费者模式,实现两种操作的解耦。

生产者-消费者模式Producer-Consumer

Worker Thread 模式类比的是工厂里车间工人的工作模式。但其实在现实世界,工厂里还有一种流水线的工作模式,类比到编程领域,就是生产者 - 消费者模式。

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

image

未来模式Future

Future未来模式是一种异步处理的模式,就是在针对处理事件比较长的任务时,创建一个异步线程来处理任务,返回一个Future的引用,等到后面必须要要结果的时候,可以通过Future的引用来获取异步线程处理的结果。这样可以提高程序的吞吐量,减少用户等待时间。

参考资料

JAVA并发编程知识总结(全是干货超详细):https://zhuanlan.zhihu.com/p/362843892

Java 多线程模式 —— Guarded Suspension 模式:https://cloud.tencent.com/developer/article/2003740

15-并发设计模式:https://www.cnblogs.com/lusaisai/p/15983313.html (主要参考)

目录
相关文章
|
3月前
|
人工智能 安全 调度
Python并发编程之线程同步详解
并发编程在Python中至关重要,线程同步确保多线程程序正确运行。本文详解线程同步机制,包括互斥锁、信号量、事件、条件变量和队列,探讨全局解释器锁(GIL)的影响及解决线程同步问题的最佳实践,如避免全局变量、使用线程安全数据结构、精细化锁的使用等。通过示例代码帮助开发者理解并提升多线程程序的性能与可靠性。
126 0
|
6天前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
37 2
|
6天前
|
设计模式 消息中间件 安全
【JUC】(3)常见的设计模式概念分析与多把锁使用场景!!理解线程状态转换条件!带你深入JUC!!文章全程笔记干货!!
JUC专栏第三篇,带你继续深入JUC! 本篇文章涵盖内容:保护性暂停、生产者与消费者、Park&unPark、线程转换条件、多把锁情况分析、可重入锁、顺序控制 笔记共享!!文章全程干货!
45 1
|
5月前
|
Java 开发者 Kotlin
华为仓颉语言初识:并发编程之线程的基本使用
本文详细介绍了仓颉语言中线程的基本使用,包括线程创建(通过`spawn`关键字)、线程名称设置、线程执行控制(使用`get`方法阻塞主线程以获取子线程结果)以及线程取消(通过`cancel()`方法)。文章还指出仓颉线程与Java等语言的差异,例如默认不提供线程名称。掌握这些内容有助于开发者高效处理并发任务,提升程序性能。
178 2
|
5月前
|
设计模式 运维 监控
并发设计模式实战系列(4):线程池
需要建立持续的性能剖析(Profiling)和调优机制。通过以上十二个维度的系统化扩展,构建了一个从。设置合理队列容量/拒绝策略。动态扩容/优化任务处理速度。检查线程栈定位热点代码。调整最大用户进程数限制。CPU占用率100%
353 0
|
Java 程序员 调度
【JAVA 并发秘籍】进程、线程、协程:揭秘并发编程的终极武器!
【8月更文挑战第25天】本文以问答形式深入探讨了并发编程中的核心概念——进程、线程与协程,并详细介绍了它们在Java中的应用。文章不仅解释了每个概念的基本原理及其差异,还提供了实用的示例代码,帮助读者理解如何在Java环境中实现这些并发机制。无论你是希望提高编程技能的专业开发者,还是准备技术面试的求职者,都能从本文获得有价值的见解。
190 1
|
9月前
|
安全 Java 程序员
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
249 11
|
11月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
781 6
|
11月前
|
设计模式 安全 Java
Java 多线程并发编程
Java多线程并发编程是指在Java程序中使用多个线程同时执行,以提高程序的运行效率和响应速度。通过合理管理和调度线程,可以充分利用多核处理器资源,实现高效的任务处理。本内容将介绍Java多线程的基础概念、实现方式及常见问题解决方法。
369 1
|
11月前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####

热门文章

最新文章