多线程(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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

hdu1180(广搜+优先队列)

此题要求最少到达目标点T的最短时间,所以我选择了广度优先搜索,并且要用到优先队列。 另外此题注意点较多,比如说可以在某个点停留,我wa了好多两次,就是因为忽略了这一点,然后参考了大神的思想,然后经过反复修改才AC的 这是我的代码 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

poj 3190 优先队列+贪心

题意: 有n头牛,分别给他们挤奶的时间。 然后每头牛挤奶的时候都要在一个stall里面,并且每个stall每次只能占用一头牛。 问最少需要多少个stall,并输出每头牛所在的stall。 e.g 样例: INPUT: 51 102 43 65 84 7 OUTPUT: 412324 HINT: Explanation of the s

poj 2431 poj 3253 优先队列的运用

poj 2431: 题意: 一条路起点为0, 终点为l。 卡车初始时在0点,并且有p升油,假设油箱无限大。 给n个加油站,每个加油站距离终点 l 距离为 x[i],可以加的油量为fuel[i]。 问最少加几次油可以到达终点,若不能到达,输出-1。 解析: 《挑战程序设计竞赛》: “在卡车开往终点的途中,只有在加油站才可以加油。但是,如果认为“在到达加油站i时,就获得了一

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

【Tools】大模型中的自注意力机制

摇来摇去摇碎点点的金黄 伸手牵来一片梦的霞光 南方的小巷推开多情的门窗 年轻和我们歌唱 摇来摇去摇着温柔的阳光 轻轻托起一件梦的衣裳 古老的都市每天都改变模样                      🎵 方芳《摇太阳》 自注意力机制(Self-Attention)是一种在Transformer等大模型中经常使用的注意力机制。该机制通过对输入序列中的每个元素计算与其他元素之间的相似性,

poj3750约瑟夫环,循环队列

Description 有N个小孩围成一圈,给他们从1开始依次编号,现指定从第W个开始报数,报到第S个时,该小孩出列,然后从下一个小孩开始报数,仍是报到S个出列,如此重复下去,直到所有的小孩都出列(总人数不足S个时将循环报数),求小孩出列的顺序。 Input 第一行输入小孩的人数N(N<=64) 接下来每行输入一个小孩的名字(人名不超过15个字符) 最后一行输入W,S (W < N),用

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

POJ2010 贪心优先队列

c头牛,需要选n头(奇数);学校总共有f的资金, 每头牛分数score和学费cost,问合法招生方案中,中间分数(即排名第(n+1)/2)最高的是多少。 n头牛按照先score后cost从小到大排序; 枚举中间score的牛,  预处理左边与右边的最小花费和。 预处理直接优先队列贪心 public class Main {public static voi