多线程编程

多线程写作类

倒计时协调器CountDownLatch

某个线程需要等待其他线程执行到特定操作结束即可。例如:在多Web服务中,在启动指定服务时需要启动若干启动过程中比较耗时的服务,为了尽可能减少服务启动过程的总耗时,该服务会使用专门的工程线程以并发的方式去启动这些服务。但是在这些服务启动完成后,需要对这些启动的服务进行检查,之后都启动完成后,才可以启动指定服务。

注意点:

  • 确保所有CountDownLatch.
  • ()调用都位于代码中正确的位置。最好是放在finally中,避免因线程出现异常导致,导致该线程一直处于WAITING状态。
  • 等待线程在等待先决操作完成的时间指定一个时间限制CountDownLatch.await(long, TimeUnit)。在超过指定时间后,在该时间CountDownLatch的计数器仍未到达0,那么所有执行该实例的await方法的线程都会被唤醒。

栅栏(CyclicBarrier)

多个线程都到了某一点之后在一起运行。例如,在web服务中,我们需要保证多个关联服务都启动完成后,确保在进行服务间的可以正常通信。

阻塞队列

ArrayBlockingQueue
适合在生产者线程和消费者线程之间的并发程度较大的情况下使用

  • LinkedBlockingQueue
    适合在生产者线程和消费者线程之间的并发成度较低的情况下使用
  • SynchronousQueue
    适合在消费者处理能力与生产者处理能力差不多的情况下使用

限购

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Semaphore;public class SemaphoreBasedChanne<E> implements Channel<E> {private final BlockingQueue<E> queue;private final Semaphore semaphore;/***** @param queue*          阻塞队列* @param flowLimit*          流量县限制数*/public SemaphoreBasedChanne(BlockingQueue<E> queue, int flowLimit) {this(queue, false, flowLimit);}public SemaphoreBasedChanne(BlockingQueue<E> queue, boolean isFair, int flowLimit) {this.queue = queue;this.semaphore = new Semaphore(flowLimit, isFair); // 是否是公平锁}/*** 注意:在代码中acquire与release总是配对出现的*/@Overridepublic void put(E product) throws InterruptedException {semaphore.acquire();try {queue.put(product);} finally {// 最好放在finally中,以免资源访问出现异常的情况下当前线程所获得配额无法返还semaphore.release();}}@Overridepublic E take() throws InterruptedException {return queue.take();}
}/*** 对传输通道的抽象* * @author Viscent Huang*/
public interface Channel<P> {/*** 往传输通道中存入一个产品* * @param product*          产品*/void put(P product) throws InterruptedException;/*** 从传输通道中取出一个产品* * @return 产品*/P take() throws InterruptedException;
}

管道:线程间的直接输入与输出

PipedOutputStream和PipedInputStream是生产者-消费者模式的一个具体例子。可以看作,一个线程的输出可作为另外一个线程的输入,而不必借用文件、数据库、网络连接等其他数据交换中介。

双缓冲与Exchanger

线程中断

并发集合

  • 快照:是在Iterator实例呗创建的那一刻待遍历对象内部结构的一个只读副本,它反映了待遍历集合的某一个时刻(即Iterator实例呗创建的那一刻)的状态。不同线程在读取数据时会获取到各自的快照,因此这些快照相当于这些线程的线程的线程特有对象。所以,这种方式下进行遍历操作的线程无须加锁就可以实现线程安全。另外,由于快照是只读的,因此这种遍历方式锁返回的Iterator实例是不支持remove方法的。优点: 遍历操作和更新操作之间互不影响,缺点当遍历的集合较大时,创建快照的直接或者渐渐开销会比较大。
  • 准实时:是指遍历操作不是针对待遍历对象的副本进行的,但又不借助锁来保障线程安全,从而使得遍历操作可以与更新操作并发进行。

几种线程安全集合类

非线层安全对象并发集合类共同接口遍历实现方式
ArrayListCopyOnWriteArrayListList快照
HashSetCopyOnWriteArraySetSet快照
LinkedListConcurrentLinkedQueueQueue准实时
HashMapConcurrentHashMapMap准实时
TreeMapConcurrentSkipListMapSortedMap准实时
TreeSetConcurrentSortedSet准实时
  • ConcurrentLinkedQueue:是Queue接口的一个线程安全实现类,他相当于LinkedList的线程安全版,可以作为Collections.synchronizedList(new ArryaList())的代替品。其内部访问共享状态变量(如队首和队尾指针)的时候并不借助锁,而是使用CAS操作来保障线程安全。因此ConcurrentLinkedQueue是非阻塞的,其使用不会导致当前线程被暂停,因此也就避免了上下文切换的开销。其更适合用于更新操作和遍历操作并发的场景,例如:生产者-消费者模式中生产者线程王队列中添加元素(产品),而消费者线程从队列中移除(消费)元素。
  • ConcurrentHashMap:是Map接口的一个线程安全实现类,它相当于HashMap的线程安全版,可以作为HashTable和Collections.synchronizedMap(new HashMap())的替代品。ConcurrentHashMap内部使用了颗粒度极小的锁来保障其线程安全。ConcurrentHashMap的读取操作基本上不会导致锁的使用。另外,默认ConcurrentHashMap可以支持16个并发更新线程(并发数量可以通过concurrencyLevel来调节,值越大开销越大,越小可能导致并发更新产生锁的争用),即这些线程可以在不导致锁的争用情况下进行并发更新。因此ConcurrentHashMap可以支持比较高的并发性,并且其锁的开销一般比较小。与HashTable的区别在于:锁的力度不同,HashTable在大多数方法上加锁导致,而ConcurrentHashMap是进行分段加锁的。
  • CopyOnWriteArrayList:是List接口的一个线程安全实现类,它相当于ArrayList的线程安全版本。其内部会维护一个实例变量用array用于引用一个数组。CopyOnWriteArrayList的更新操作是通过创建一个新的数组newArray,并把老的数组的内容不知道newArray,然后对newArray进行更新并将array引用指向newArray,因此CopyOnWriteArrayList适用遍历操作元比更新操作频繁或者不希望在遍历的时候加锁的场景。而其他场景下,我们仍然要考虑适用Collections.synchronizedList(new ArrayList())。
  • CopyOnWriteArraySet:是Set接口的一个线程安全实现类,它相当于HashSet的线程安全版,其内部使用一个CopyOnWriteArrayList实例,因此CopyOnWriteArraySet的使用场景与CopyOnWriteArrayList相似。

死锁(鹬蚌相争)

在这里插入图片描述
死锁:是一种在线程中常见活性故障,如果两个或者更多的线程因相互等待而被永远暂停。

死锁的检测

使用jdk自带的Jconsole查看死锁的线程以及代码的位置信息。
在这里插入图片描述

线程管理

线程的未捕获异常与监控

当线程的run方法抛出未被捕获的异常,run方法会退出,相应的线程也提前终止。对于线程的这种异常终止做一些监控以及补偿。

import com.dengsheng.thread.util.Debug;
import com.dengsheng.thread.util.Tools;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;public class ThreadMonitorDemo {volatile boolean inited = false;static int threadIndex = 0;final static Logger LOGGER = Logger.getAnonymousLogger();final BlockingQueue<String> channel = new ArrayBlockingQueue<String>(100);public static void main(String[] args) throws InterruptedException {ThreadMonitorDemo demo = new ThreadMonitorDemo();demo.init();for (int i = 0; i < 100; i++) {demo.service("test-" + i);}Thread.sleep(2000);System.exit(0);}public synchronized void init() {if (inited) {return;}Debug.info("init...");WokrerThread t = new WokrerThread();t.setName("Worker0-" + threadIndex++);// 为线程t关联一个UncaughtExceptionHandlert.setUncaughtExceptionHandler(new ThreadMonitor());t.start();inited = true;}public void service(String message) throws InterruptedException {channel.put(message);}private class ThreadMonitor implements Thread.UncaughtExceptionHandler {@Overridepublic void uncaughtException(Thread t, Throwable e) {Debug.info("Current thread is `t`:%s, it is still alive:%s",Thread.currentThread() == t, t.isAlive());// 将线程异常终止的相关信息记录到日志中String threadInfo = t.getName();LOGGER.log(Level.SEVERE, threadInfo + " terminated:", e);// 创建并启动替代线程LOGGER.info("About to restart " + threadInfo);// 重置线程启动标记inited = false;init();}}// 类ThreadMonitor定义结束private class WokrerThread extends Thread {@Overridepublic void run() {Debug.info("Do something important...");String msg;try {for (;;) {msg = channel.take();process(msg);}} catch (InterruptedException e) {// 什么也不做}}private void process(String message) {Debug.info(message);// 模拟随机性异常int i = (int) (Math.random() * 100);if (i < 2) {throw new RuntimeException("test");}Tools.randomPause(100);}}// 类ThreadMonitorDemo定义结束
}

异步编程

从任务角度来看,任务分为两种:

  • 同步(Synchronous),如同接力赛跑,一个任务好比一个田径运动员,比赛时,一个运动从开始跑到下个队员位置,下一个队员接着跑。
  • 异步(Asychronous),如同接力赛跑,一个任务好比一个队伍,比赛时,队伍只负责跑,至于结果需要等组委会给最后的结果。

Java中:
假设我们用一个Runable实例task来表示一个任务,我们直接调用task.run()来执行该任务,此时这个任务就是同步任务,如果使用new Thread(task).start()调用一个专门的工作者线程来执行该任务,或者将该任务提交给Executor实例executor执行,此时该任务为异步任务。

同步与异步是相对的,它取决于任务的执行方式以及我们的观察角度。

ExecutorService的实现例
方法使用条件以及注意实现
newCachedThreadPool适用于执行大量耗时较短且提交比较频繁的任务
newFixedThreadPool此线程池核心线程数等于最大线程数,所以线程不会超时关闭,在使用该线程池的时候需要手动关闭该线程池,释放资源
newSingleThreadExecutor适用单(多)生产者-单消费者模式
newScheduledThreadPool周期性线程池,周期性执行队列中的任务,多个线程
newSingleThreadScheduledExecutor周期性线程池,周期性执行队列中的任务,单个线程

前三种类的是由 ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,workQueue)演化而来的。

  • newCachedThreadPool :: new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());

    0:最大核心数为0,即SynchronousQueue中的第一队列任务会被执行,而其他的需要等前面的执行完成后才执行。
    Integer.MAX_VALUE:线程池中最大的线程数,当现在执行的线程数没有超过且没有空闲线程的时候,线程池会立即启动一个新的线程执行任务。极端情况下,在同时执行的任务数量小于Integer.MAX_VALUE时,这些提交的任务都会启动一个新的线程,此时就会造成线程的频繁切换,拖累整个系统。同时还可能会造成OOM(线程数过多,需要的的内存量过大)
    60L:线程池中的线程等待时间超过60s后,会自动销毁。

  • newFixedThreadPool::new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));

    0L: 线程不会超时,即线程不会被因超时而销毁。
    nThreads: 核心线程数与最大线程数相同,即线程池中的线程不会被销毁。当我们不再使用该线程池时,应该手动关闭线程池。尽管FinalizableDelegatedExecutorService在jvm回收的时候会关闭该线程池,但是还是及时关闭该线程池。

    static class FinalizableDelegatedExecutorServiceextends DelegatedExecutorService {FinalizableDelegatedExecutorService(ExecutorService executor) {super(executor);}protected void finalize() {super.shutdown();}
    }
    
  • newSingleThreadExecutor::new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()))

    0L: 线程不会超时,即线程不会被因超时而销毁。
    1: 最大核心数和最大线程数都只有一个,所以只能有一个线程处理任务。例如,往某个文件里面写多组数据,每组数据获取时间不同,先处理完的先写,之后数据放到线程池里处理(当然在满足效率的情况下,既可以节省内存,还可以避免一些没有必须要的问题,例如死锁)。

  • newScheduledThreadPool::new ScheduledThreadPoolExecutor(corePoolSize)和newSingleThreadScheduledExecutor::new ScheduledThreadPoolExecutor(1)

    newScheduledThreadPool 和 newSingleThreadScheduledExecutor都是周期性线程池。区别在于:核心线程可能不同
    schedule与scheduleWithFixedRate不太一样,schedule当任务执行的时间超过周期的时间,会导致后面的任务都会产生时间偏移)

异步任务的批量执行:CompletionService

没有见过,暂时不学

java内存模型

高速缓存一致性

synchronized

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/746637.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入探讨MES管理系统与MOM系统之间的关系

在制造业的信息化浪潮中&#xff0c;各种系统与技术层出不穷&#xff0c;其中MES制造执行系统和MOM制造运营管理无疑是备受瞩目的两大主角。尽管它们都是制造业信息化不可或缺的部分&#xff0c;但许多人对它们之间的区别与联系仍感到困惑。本文将对MES管理系统和MOM系统进行深…

uniapp小程序上传oss

uniapp上传小程序代码 import crypto from crypto-js; import { Base64 } from js-base64/base64.js; // 计算oss签名。 function computeSignature(accessKeySecret, canonicalString) {return crypto.enc.Base64.stringify(crypto.HmacSHA1(canonicalString, accessKeySecre…

#数据结构 线性表的顺序存储

目录 每日文案 一、线性表的定义 二、线性表的操作 顺序表的存储结构 顺序表的初始化操作 判断顺序表是否为空表 将顺序表置为空表 计算顺序表中的元素个数 取出顺序表中的对应位置元素 取出对应数值的位序 在对应位置插入元素 将对应位置的元素删除 将顺序表中的数据…

1.Python数据分析—数据分析与挖掘详讲

1.Python数据分析—数据分析与挖掘详讲 一个人简介二数据分析与挖掘概述三什么是数据分析和挖掘四数据分析与挖掘在不同领域的应用4.1医疗领域&#xff1a;4.1.1 建立疾病数据库&#xff1a;4.1.2 临床决策支持&#xff1a;4.1.3 疾病预警和监控&#xff1a; 4.2 电子商务领域&…

Linux下platform驱动框架编写

一. 简介 前面一篇文章简单学习了 Linux内核中platform驱动代码。文章地址如下&#xff1a; Linux下platform驱动简介-CSDN博客 本文学习编写 platform驱动框架代码。 二. Linux下platform驱动框架编写 1. 编写platform驱动代码的思路 &#xff08;1&#xff09; 定义结…

第12章 指针

以下内容是学习尚硅谷 12.1 指针基本介绍 1&#xff09;指针是C语言的精华&#xff0c;也是C语言的难点 2&#xff09;指针&#xff0c;也就是内存的地址&#xff1b;所谓指针变量&#xff0c;也就是保存了内存地址的变量。关于指针的基本使用&#xff0c;在讲变量的时候做了…

WAPI模块AT指令驱动

一、前言 由于项目更迭&#xff0c;需要将原4G模块更换为国内的WAPI协议模块&#xff0c;主控芯片NRF52840无需改动其他部分&#xff0c;只需要将串口部分的数据格式稍作更改即可。 编程风格和之前的esp8266一致&#xff0c;同样都是AT指令来配置模块&#xff0c;由于主…

Java封装的优点

目录 提高代码可维护性 降低耦合度 保护数据安全性 封装如何提高安全性 数据隐藏 访问控制 限制接口 错误隔离

请解释Redis是什么?它有哪些主要应用场景?Redis支持哪些数据类型?并描述每种数据类型的特性和使用场景。

请解释Redis是什么&#xff1f;它有哪些主要应用场景&#xff1f; Redis是一款内存高速缓存NoSQL数据库&#xff0c;使用C语言编写&#xff0c;它支持丰富的数据类型&#xff0c;如String、list、set、zset、hash等&#xff0c;并且这些数据类型都直接支持数据的原子性操作&…

d2-crud-plus 使用小技巧(四)—— 搜索限制只能输入数字

需求 搜索时有些字段需要限制&#xff0c;比如只能输入数字&#xff0c;不能存在其他字符包括空格。 效果 事情焦点后先触发校验&#xff0c;在触发查询。 代码 crud.js export const crudOptions (vm) > {return {columns: [{title: 号码,key: number,search: { //…

比Let‘s Encrypt更简单更齐全的免费证书申请教程

步骤一 打开JoySSL官网&#xff0c;注册属于你的专属账号&#xff1b; 永久免费SSL证书申请地址真正完全且永久免费&#xff01;不用您花一分钱&#xff0c;SSL证书免费使用90天&#xff0c;并且还支持连续签发。JoySSL携手全球权威可信顶级根&#xff0c;自研新一代SSL证书&…

打zip包,支持有空文件夹

支持有空文件夹 // 打包String url dir0 File.separator dir1;log.info("将此文件夹打成zip包&#xff1a;"url);String urlZip dir0 File.separator dir1 ".zip";File file new File(url);// 方法2&#xff1a;压缩包内支持空文件夹ZipUtil.zip(F…

【汇编】#3 8086与数据有关的寻址方式

文章目录 操作码与操作数1. 8086处理器的与数据有关的寻址方式1.1 立即数寻址方式1.2 寄存器寻址方式 2. 有效&#xff08;偏移&#xff09;地址&#xff08;effective address&#xff0c;EA&#xff09;与缺省段寄存器选择tips:段跨越前缀2.1 直接寻址tips:直接寻址与立即寻址…

GitOps实践之Argo CD (2)

argocd 【-1】argocd可以解决什么问题? helm 部署是手动的?依赖流水线。而有时候仅仅更新一个小东西,流水线跑好久,CD真的不应该和CI耦合。不同环境的helm配置不同,手动修改问题多,可以用git管理起来,例如分不同环境用目录区分。argocd创建应用可以不通环境部署到不同集…

Seata 2.x 系列【12】高可用集群部署

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Seata 版本 2.0.0 本系列Spring Boot 版本 3.2.0 本系列Spring Cloud 版本 2023.0.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-seata-demo 文章目录 1. 概述2. 搭建演…

Linux下新增有root权限的用户

步骤&#xff1a; 1.以 root 用户身份登录到 CentOS 服务器。 2.使用以下命令创建新用户&#xff08;将 newuser 替换为您想要创建的用户名&#xff09;&#xff1a; sudo adduser username 3.为新用户设置密码&#xff1a; sudo passwd username 按照提示输入新增用户密码 …

计算机基础1-汇编基础

汇编语言是一种低级的计算机语言&#xff0c;它直接与计算机硬件进行交互。在汇编语言中&#xff0c;指令由一系列助记符&#xff08;mnemonic&#xff09;组成&#xff0c;用于执行特定的操作&#xff0c;如数据传输、算术运算和控制流程等。每个指令都对应着一条机器码&#…

《数据安全法》关于数据出境的条款

《数据安全法》关于数据出境的条款 《中华人民共和国数据安全法》于2021年6月10日通过&#xff0c;2021年9月1日起实施&#xff09;。 在《数据安全法》中&#xff0c;第三十一条&#xff1a;“关键信息基础设施的运营者在中华人民共和国境内运营中收集和产生的重要数据的出境…

Golang如何使用命令行-- flag库

参考文献&#xff1a; flag package - flag - Go Packages 使用&#xff1a; import "flag" var nFlag flag.Int("n", 1234, "help message for flag n") 上述方法返回的是一个指针变量nFlag&#xff0c;如果我们要打印&#xff0c;应该使用…

声卡喊话IP喇叭,IP网络吸顶天花喇叭

声卡喊话IP喇叭&#xff0c;IP网络吸顶天花喇叭 SV-7043VP是一款ip/sip网络吸顶喇叭&#xff0c;具有10/100M以太网接口&#xff0c;从网络接口接收网络的音频数据后播放。本网络吸顶喇叭可以与其他广播主机、服务器软件和采播主机配合使用&#xff0c;实现音频的播放&#xf…