多线程(Lock锁,死锁,等待唤醒机制,阻塞队列,线程池)

2024-06-21 00:44

本文主要是介绍多线程(Lock锁,死锁,等待唤醒机制,阻塞队列,线程池),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作Lock中提供了获得锁和释放锁的方法
void lock():获得锁void unlock():释放锁
即手动上锁、手动释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例


例子:

package Threadmethod;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class MyThread1 extends Thread{static int jicket=0;static Lock lock=new ReentrantLock();@Overridepublic void run() {while (true){lock.lock();try {if(jicket==100){break;}else {Thread.sleep(10);jicket++;System.out.println(getName()+ "正在卖第"+jicket+"张票");}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}}
}

测试类 

package Threadmethod;public class ThreadDemo8 {public static void main(String[] args) {MyThread1 t1=new MyThread1();MyThread1 t2=new MyThread1();MyThread1 t3=new MyThread1();t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}
}

死锁

死锁是指在多个进程或线程中,每个进程或线程因为等待另一个进程或线程所拥有的资源而进入无限等待的状态,导致系统无法继续执行下去的情况。死锁通常发生在以下情况下:

1. 互斥:多个进程或线程同时只能持有一个资源,如果一个进程或线程占用了一个资源,其他进程或线程就无法访问该资源。

2. 请求与保持:进程或线程在等待其他进程或线程所拥有的资源时,继续持有已经占用的资源。

3. 不可抢占:已经占用了某个资源的进程或线程不能被其他进程或线程抢占。

4. 循环等待:多个进程或线程形成一个循环等待资源的关系,每个进程或线程都在等待下一个进程或线程所拥有的资源。

死锁的解决方法包括:

1. 预防:通过破坏四个必要条件中的一个或多个条件来预防死锁的发生。

2. 避免:在资源分配的时候,使用一种资源分配算法来避免可能引发死锁的情况。

3. 检测与恢复:周期性地检测系统中是否存在死锁,如果发现死锁,则采取恢复策略(如抢占资源或终止进程)解除死锁。

4. 忽略:有些系统选择忽略死锁的发生,因为死锁发生的概率较低,解除死锁所需的系统开销较大。

package Thread1;public class MyThread extends Thread {static   Object objA = new Object();static Object objB = new Object();@Overridepublic void run() {while (true) {if ("线程A".equals(getName())) {synchronized (objA) {System.out.println("线程A拿到了A锁,准备拿锁");//A锁要拿到B锁才能继续synchronized (objB) {System.out.println("线程A拿到了B锁,顺利执行完一轮");}}}else if ("线程B".equals(getName())) {if ("线程B".equals(getName())) {synchronized (objB) {System.out.println("线程B拿到了B锁,准备拿A锁");//B锁要拿到A锁才能继续synchronized (objA) {System.out.println("线程B拿到了A锁,顺利执行完一轮");}}}}}}
}

这就是死锁, A锁要拿到B锁才能继续,B锁要拿到A锁才能继续

等待唤醒机制

等待唤醒机制(Wait/Notify Mechanism)是指线程之间的一种协作机制,用于解决多线程并发执行中的同步与通信问题。

在等待唤醒机制中,一个线程可以调用wait()方法进入等待状态,同时释放对象锁;而另一个线程则可以调用notify()或者notifyAll()方法来唤醒处于等待状态的线程,并使其进入就绪状态,以便于执行。

等待唤醒机制本质上是基于对象的监视器(Monitor)实现的。每个对象都有一个与之关联的监视器,用于控制对该对象的访问。当一个线程调用了该对象的wait()方法后,该线程就会释放该对象的监视器,并进入等待队列,直到被其他线程调用notify()或者notifyAll()方法来唤醒。

等待唤醒机制常用于生产者-消费者模型、读写锁模型等场景。通过等待唤醒机制,线程之间可以协调合作,实现数据的安全共享与交换。

void wait()        当前线程等待,直到被其他线程唤醒
void notify()        随机唤醒单个线程
void notifyAll()        唤醒所有线程 

写个例子

Desk类:

package waitandnotify;public class Desk {//是否有面条 0:没有  1:有public static int foodFlag=0;//总个数public static int count=10;//锁对象public static Object lock=new Object();}

Foodie类

package waitandnotify;public class Foodie extends Thread{@Overridepublic void run() {///循环while(true){// 同步代码块synchronized (Desk.lock){//   判断共享数据是否到了末尾(到了末尾)if (Desk.count==0){break;}else{// 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)//先判断桌子上是否有面条if (Desk.foodFlag==0){//如果没有就等待try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else {//把吃的总数-1Desk.count--;//如果有就开吃System.out.println("正在吃面条,还能吃"+Desk.count+"碗");//吃完之后唤醒厨师继续做Desk.lock.notifyAll();//修改桌子的状态Desk.foodFlag=0;}}}}}
}

Cook类

package waitandnotify;public class Cook extends Thread{@Overridepublic void run() {while (true){synchronized (Desk.lock){if(Desk.count==0){break;}else {//判断桌子上是否有食物if(Desk.foodFlag==1) {// 如果有,就等待try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else {// 如果没有,就制作食物System.out.println("厨师做了一碗面条");// 修改桌子上的食物状态Desk.foodFlag=1;//叫醒消费者开吃Desk.lock.notifyAll();}}}}}
}

测试类

package waitandnotify;public class ThreadDemo {public static void main(String[] args) {Cook c=new Cook();Foodie f=new Foodie();c.setName("厨师");f.setName("吃货");c.start();f.start();}
}

阻塞队列 

阻塞队列(Blocking Queue)是一种特殊的队列,其在插入和删除元素时,当队列已满或为空时会阻塞等待。阻塞队列常用于多线程编程中,用于实现线程间的安全通信。

阻塞队列的主要特点是:当队列为空时,从队列中取出元素的操作将会被阻塞,直到队列中有新的元素被插入;当队列已满时,将元素插入队列的操作将会被阻塞,直到队列中有空位。

阻塞队列有多种实现方式,常见的有:
1. ArrayBlockingQueue:基于数组实现的有界阻塞队列,按照 FIFO(先进先出)的顺序对元素进行存取。
2. LinkedBlockingQueue:基于链表实现的可选有界或无界阻塞队列,按照 FIFO 的顺序对元素进行存取。
3. SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等待一个相应的删除操作,反之亦然。
4. PriorityBlockingQueue:基于优先级堆实现的阻塞队列,元素按照优先级进行存取。

阻塞队列的使用可以简化多线程编程中的线程同步操作,保证线程安全,并提供了一种有效的方式来实现生产者-消费者模型。

 线程的状态

线程池

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象

public static ExecutorService newCachedThreadPool()创建一个没有上限的线程池

MyRunnable类:

package threadpool;public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+"-----"+i);}}
}

测试:

package threadpool;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolDemo {public static void main(String[] args) {//获取线程池对象ExecutorService pool1 = Executors.newCachedThreadPool();//提交任务pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());}
}


可以看到有6个线程

public static ExecutorService newFixedThreadPool (int nThreads)创建有上限的线程池

测试

package threadpool;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolDemo2 {public static void main(String[] args) {//获取线程池对象ExecutorService pool1 = Executors.newFixedThreadPool(3);//提交任务pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());}
}

只有三个线程

线程池主要核心原理

  1. 创建一个池子,池子中是空的
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

自定义线程池

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,时间的单位,任务队列,创建线程工厂,任务的拒绝策略);

参数一:核心线程数量        不能小于θ        
参数二:最大线程数        不能小于等于0,最大数量 >= 核心线程数量
参数三:空闲线程最大存活时间                不能小于θ
参数四:时间单位        用TimeUnit指定
参数五:任务队列                不能为null
参数六:创建线程工厂        不能为null
参数七:任务的拒绝策略        不能为null

package threadpool;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class ThreadPoolDemo3 {public static void main(String[] args) {ThreadPoolExecutor pool=new ThreadPoolExecutor(3,//核心线程数量,能小于06,//最大线程数,不能小于0,最大数量>=核心线程数量60,//空闲线程最大存活时间TimeUnit.SECONDS,//时间单位秒new ArrayBlockingQueue<>(3),//任务队列Executors.defaultThreadFactory(),//创建线程工厂new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略);}
}

不断的提交任务,会有以下三个临界点:

  1. 当核心线程满时,再提交任务就会排队
  2. 当核心线程满,队伍满时,会创建临时线程
  3. 当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略 

任务拒绝策略

ThreadPoolExecutor.AbortPolicy        丢弃任务并抛出ReiectedExecutionException异常 
ThreadPoolExecutor.DiscardPolicy        丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardoldestPolicy  抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy        调用任务的run()方法绕过线程池直接执行 

最大并行数

多线程的最大并行数理想情况下应为CPU核心数的两倍,即2N,在IO密集型运算中;而在CPU密集型运算中,则建议设置为CPU核心数加一,即N+1

多线程技术是现代计算机编程中不可或缺的一部分,尤其是在处理高并发、高性能需求时,合理设置多线程的最大并行数能显著提高程序的效率和稳定性。这涉及到两种主要的任务类型:IO密集型和CPU密集型。对于IO密集型任务,由于大量的时间花在等待IO操作(如网络请求或数据库查询)上,因此可以设置更多的线程以保持CPU的高效利用。相反,CPU密集型任务则主要集中在处理器运算上,过多的线程反而可能导致频繁的上下文切换,降低效率。


这一期就到这里啦

努力遇见更好的自己!!!

这篇关于多线程(Lock锁,死锁,等待唤醒机制,阻塞队列,线程池)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1079728

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

Java中如何正确的停掉线程

《Java中如何正确的停掉线程》Java通过interrupt()通知线程停止而非强制,确保线程自主处理中断,避免数据损坏,线程池的shutdown()等待任务完成,shutdownNow()强制中断... 目录为什么不强制停止为什么 Java 不提供强制停止线程的能力呢?如何用interrupt停止线程s

RabbitMQ 延时队列插件安装与使用示例详解(基于 Delayed Message Plugin)

《RabbitMQ延时队列插件安装与使用示例详解(基于DelayedMessagePlugin)》本文详解RabbitMQ通过安装rabbitmq_delayed_message_exchan... 目录 一、什么是 RabbitMQ 延时队列? 二、安装前准备✅ RabbitMQ 环境要求 三、安装延时队

基于Redis自动过期的流处理暂停机制

《基于Redis自动过期的流处理暂停机制》基于Redis自动过期的流处理暂停机制是一种高效、可靠且易于实现的解决方案,防止延时过大的数据影响实时处理自动恢复处理,以避免积压的数据影响实时性,下面就来详... 目录核心思路代码实现1. 初始化Redis连接和键前缀2. 接收数据时检查暂停状态3. 检测到延时过

python 线程池顺序执行的方法实现

《python线程池顺序执行的方法实现》在Python中,线程池默认是并发执行任务的,但若需要实现任务的顺序执行,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋... 目录方案一:强制单线程(伪顺序执行)方案二:按提交顺序获取结果方案三:任务间依赖控制方案四:队列顺序消

Redis中哨兵机制和集群的区别及说明

《Redis中哨兵机制和集群的区别及说明》Redis哨兵通过主从复制实现高可用,适用于中小规模数据;集群采用分布式分片,支持动态扩展,适合大规模数据,哨兵管理简单但扩展性弱,集群性能更强但架构复杂,根... 目录一、架构设计与节点角色1. 哨兵机制(Sentinel)2. 集群(Cluster)二、数据分片

Python多线程实现大文件快速下载的代码实现

《Python多线程实现大文件快速下载的代码实现》在互联网时代,文件下载是日常操作之一,尤其是大文件,然而,网络条件不稳定或带宽有限时,下载速度会变得很慢,本文将介绍如何使用Python实现多线程下载... 目录引言一、多线程下载原理二、python实现多线程下载代码说明:三、实战案例四、注意事项五、总结引

Python多线程应用中的卡死问题优化方案指南

《Python多线程应用中的卡死问题优化方案指南》在利用Python语言开发某查询软件时,遇到了点击搜索按钮后软件卡死的问题,本文将简单分析一下出现的原因以及对应的优化方案,希望对大家有所帮助... 目录问题描述优化方案1. 网络请求优化2. 多线程架构优化3. 全局异常处理4. 配置管理优化优化效果1.

深入理解go中interface机制

《深入理解go中interface机制》本文主要介绍了深入理解go中interface机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前言interface使用类型判断总结前言go的interface是一组method的集合,不