多线程和并发编程(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 (主要参考)

目录
相关文章
|
12天前
|
Java
并发编程之线程池的底层原理的详细解析
并发编程之线程池的底层原理的详细解析
45 0
|
12天前
|
安全 Java
并发编程之常见线程安全类以及一些示例的详细解析
并发编程之常见线程安全类以及一些示例的详细解析
13 0
|
12天前
|
Java
并发编程之线程池的应用以及一些小细节的详细解析
并发编程之线程池的应用以及一些小细节的详细解析
17 0
|
7天前
|
SQL 开发框架 .NET
高级主题:Visual Basic 中的多线程和并发编程
【4月更文挑战第27天】本文深入探讨了Visual Basic中的多线程和并发编程,阐述了其基本概念,如何使用`System.Threading.Thread`类创建线程,以及借助`ThreadPool`、`Monitor`和`SyncLock`进行同步管理。文章还提到了多线程编程面临的挑战如竞态条件、死锁和资源竞争,并介绍了VB的异步编程、TPL和并发集合等高级技术。通过实例展示了多线程在文件处理、网络通信和图像处理中的应用,并给出了多线程编程的最佳实践。总之,理解并掌握VB的多线程和并发编程能有效提升应用程序的性能和响应能力。
|
4天前
|
Dart 前端开发 安全
【Flutter前端技术开发专栏】Flutter中的线程与并发编程实践
【4月更文挑战第30天】本文探讨了Flutter中线程管理和并发编程的关键性,强调其对应用性能和用户体验的影响。Dart语言提供了`async`、`await`、`Stream`和`Future`等原生异步支持。Flutter采用事件驱动的单线程模型,通过`Isolate`实现线程隔离。实践中,可利用`async/await`、`StreamBuilder`和`Isolate`处理异步任务,同时注意线程安全和性能调优。参考文献包括Dart异步编程、Flutter线程模型和DevTools文档。
【Flutter前端技术开发专栏】Flutter中的线程与并发编程实践
|
4天前
|
安全 调度 Swift
【Swift开发专栏】Swift中的多线程与并发编程
【4月更文挑战第30天】本文探讨Swift中的多线程与并发编程,分为三个部分:基本概念、并发编程模型和最佳实践。介绍了线程、进程、并发与并行、同步与异步的区别。Swift的并发模型包括GCD、OperationQueue及新引入的结构体Task和Actor。编写高效并发代码需注意任务粒度、避免死锁、使用线程安全集合等。Swift 5.5的并发模型简化了异步编程。理解并掌握这些知识能帮助开发者编写高效、安全的并发代码。
|
5天前
|
安全 Java 开发者
构建高效微服务架构:后端开发的新范式Java中的多线程并发编程实践
【4月更文挑战第29天】在数字化转型的浪潮中,微服务架构已成为软件开发的一大趋势。它通过解耦复杂系统、提升可伸缩性和促进敏捷开发来满足现代企业不断变化的业务需求。本文将深入探讨微服务的核心概念、设计原则以及如何利用最新的后端技术栈构建和部署高效的微服务架构。我们将分析微服务带来的挑战,包括服务治理、数据一致性和网络延迟问题,并讨论相应的解决方案。通过实际案例分析和最佳实践的分享,旨在为后端开发者提供一套实施微服务的全面指导。 【4月更文挑战第29天】在现代软件开发中,多线程技术是提高程序性能和响应能力的重要手段。本文通过介绍Java语言的多线程机制,探讨了如何有效地实现线程同步和通信,以及如
|
5天前
|
并行计算 数据处理 开发者
Python并发编程:解析异步IO与多线程
本文探讨了Python中的并发编程技术,着重比较了异步IO和多线程两种常见的并发模型。通过详细分析它们的特点、优劣势以及适用场景,帮助读者更好地理解并选择适合自己项目需求的并发编程方式。
|
10天前
|
Java
[并发编程基础] Java线程的创建方式
[并发编程基础] Java线程的创建方式
|
10天前
|
安全 Java API
Java从入门到精通:3.2.1分布式与并发编程——深入Java并发包,精通多线程高级用法
Java从入门到精通:3.2.1分布式与并发编程——深入Java并发包,精通多线程高级用法