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

news2024/10/22 14:09:23

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();
    @Override
    public 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();

    @Override
    public 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{
    @Override
    public 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 {
                        //把吃的总数-1
                        Desk.count--;
                        //如果有就开吃
                        System.out.println("正在吃面条,还能吃"+Desk.count+"碗");
                        //吃完之后唤醒厨师继续做
                        Desk.lock.notifyAll();

                        //修改桌子的状态
                        Desk.foodFlag=0;
                    }

                }

            }

        }

    }
}

Cook类

package waitandnotify;

public class Cook extends Thread{
    @Override
    public 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 {
    @Override
    public 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,//核心线程数量,能小于0
                6,//最大线程数,不能小于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密集型任务则主要集中在处理器运算上,过多的线程反而可能导致频繁的上下文切换,降低效率。


这一期就到这里啦

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

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

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

相关文章

网络编程(七)网络超时检测

文章目录 一、概念二、实现方式&#xff08;一&#xff09; 使用select实现超时检测1. select函数补充说明&#xff1a;2. 使用示例3. 输出结果 &#xff08;二&#xff09; 使用setsockopt函数1. 函数定义2. 获取发送缓冲区和接收缓冲区的大小3. 端口复用4. 设置超时时间 &…

每日复盘-202406020

今日关注&#xff1a; 20240620 六日涨幅最大: ------1--------300462--------- 华铭智能 五日涨幅最大: ------1--------300462--------- 华铭智能 四日涨幅最大: ------1--------300462--------- 华铭智能 三日涨幅最大: ------1--------300462--------- 华铭智能 二日涨幅最…

华为数通企业面试笔试实验题

1. 笔试题 1.1 实验拓扑 1.2 实验要求 公司A为小型销售公司,需要实现基本上网功能,蓝色部分为外网线,提供DHCP服务 DnsServer:114.114.114.114 帮助网管排查某一台计算机在某一台交换机的某个端口 2. 操作步骤 配置路由器相关的LAN侧接口IP地址 配置DHCP项,要求有PC1与PC2…

关于椭圆的方程(有Python画的动图)

关于椭圆的方程&#xff08;有Python画的动图&#xff09; flyfish 几何定义 椭圆是平面上所有到两个固定点&#xff08;焦点&#xff09;的距离之和为常数的点的集合。这两个固定点叫做焦点。 解析几何描述 设椭圆的两个焦点为 F 1 F_1 F1​ 和 F 2 F_2 F2​&#xff…

Redis学习|Redis主从复制、Redis哨兵模式、缓存穿透、缓存击穿、缓存雪崩概念和相应解决方法

Redis主从复制 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器。前者称为主节点(master/leader)&#xff0c;后者称为从节点(slave/follower);数据的复制是单向的&#xff0c;只能由主节点到从节点。Master以写为主&#xff0c;Slave 以…

深度解读:如何运用600+标签精准构建企业画像、快速筛选高价值企业?

启信慧眼-启信宝企业版 近年来&#xff0c;信息安全越来越得到企业与公众的重视&#xff0c;数据库作为数据基础设施的关键部件&#xff0c;是保障关键信息基础设施安全的重要前提。数据库不仅为企业提供了数据存储和管理的基础设施&#xff0c;还通过数据分析、优化和扩展等功…

php反序列化的一些知识

问题 <?php $raw O:1:"A":1:{s:1:"a";s:1:"b";}; echo serialize(unserialize($raw)); //O:1:"A":1:{s:1:"a";s:1:"b";}?> php反序列化的时按理说找不到A这个类&#xff0c;但是他没有报错&#xff0c;…

conda install xformers -c xformers/label/dev 的安装问题

在StableSR项目框架中&#xff0c;需要执行 conda install xformers -c xformers/label/dev 但是报错&#xff0c;错误显示&#xff0c;版本不匹配&#xff0c;如下所示&#xff1a; 我改用pip来安装&#xff0c;好像就不报错了&#xff1a; pip install xformers

Django使用django-apscheduler实现定时任务

定时任务可以在后台定时执行指定的代码&#xff0c;避免了很多人为操作。下面是在Django项目中如何使用定时任务的具体操作流程。 我在这里使用的 django-apscheduler库来实现定时任务。 一、安装 django-apscheduler pip install django-apscheduler二、在项目的setting.py…

vivado TIMING_PATH

描述 定时路径由设计元素之间的连接来定义。在数字中 在设计中&#xff0c;时序路径是由一对受其控制的顺序元件形成的 或者通过两个不同的时钟来启动和捕获信号。 在典型的定时路径中&#xff0c;数据在一个内的两个连续单元之间传输 时钟周期。例如&#xff0c;启动边缘发生在…

‘xxxx‘ is a program, not an importable package

程序出现报错&#xff1a;‘xxxx’ is a program, not an importable package&#xff1a;“xxxx”是一个程序&#xff0c;而不是一个可导入的包 当程序出现这种报错提示的时候&#xff0c;首先第一点要做的就是需要使用到的某个方法/函数/结构体等所在的.go文件上面的packag…

音视频开发—RTMP推流详解

RTMP&#xff08;Real-Time Messaging Protocol&#xff0c;实时消息传输协议&#xff09;是一种用于音视频和数据在互联网进行实时传输的协议&#xff0c;最初由Macromedia&#xff08;现Adobe&#xff09;开发&#xff0c;用于与Adobe Flash Player进行通信。RTMP被广泛用于流…

成都晨持绪:新手开抖音网店什么品好做

在如今这个互联网高速发展的时代&#xff0c;抖音作为一款流行的短视频平台&#xff0c;已经成为许多创业者开展线上业务的首选之地。对于刚入行的新手来说&#xff0c;开设一个抖音网店既是一个机遇也是挑战&#xff0c;选择什么样的商品销售成为成功的关键因素之一。 首要的是…

数据库异常恢复2-备份文件恢复(快速恢复的手动启动方式)

(四) 备份文件备份恢复的概念 本次所说的数据恢复有异于数据的导入导出 1. 备份工具 gbase8s数据库提供了两种工具进行完成系统物理备份、逻辑日志备份和系统恢复&#xff1a;ontape和onbar ontape&#xff1a;提供了基本的系统物理备份、日志备份和恢复能力&#xff0c;其…

java实现分类下拉树,点击时对应搜索---后端逻辑

一直想做分类下拉&#xff0c;然后选择后搜索的页面&#xff0c;正好做项目有了明确的需求&#xff0c;查找后发现el-tree的构件可满足需求&#xff0c;数据要求为&#xff1a;{ id:1, label:name, childer:[……] }形式的&#xff0c;于是乎&#xff0c;开搞&#xff01; 一…

QT自定义标题栏窗口其二:实现拖动及可拉伸效果 + 顶部全屏/侧边半屏

1、效果 2、核心代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent<

管理咨询公司的五个招聘秘密

在管理咨询中&#xff0c;人是业务&#xff1b;客户支付数百万美元&#xff0c;要求管理顾问确认问题&#xff0c;并推荐解决方案。由于收入和合规性受到威胁&#xff0c;招聘错误的成本可能非常昂贵&#xff0c;一些公司更倾向于谨慎而不是效率。然而&#xff0c;在当今竞争激…

掌握大型语言模型的 7 个基本步骤

介绍 LLMs 正在改变我们今天与科技互动的方式。这些人工智能程序能够理解和模仿人类语言。它们可以应用于数据分析、客户服务、内容创作和其他领域。但对于新手来说&#xff0c;了解如何使用它们似乎很有挑战性。本文将引导读者了解掌握大型语言模型的 7 个基本步骤。 本文还…

ROM以及ROM与RAM对比

1.ROM ROM最原始的定义是“只读存储器”&#xff0c;一旦写入原始信息则不能更改。所以ROM通常用来存放固定不变的程序、常数和汉字字库&#xff0c;甚至用于操作系统的固化。它与随机存储器可共同作为主存的一部分&#xff0c;统一构成主存的地址域。 现在已经发展出了很多R…

AI 情感聊天机器人之旅 —— 相关论文调研

开放域闲聊场景 Prompted LLMs as Chatbot Modules for Long Open-domain Conversation 发布日期&#xff1a;2023-05-01 简要介绍&#xff1a;作者提出了 MPC&#xff08;模块化提示聊天机器人&#xff09;&#xff0c;这是一种无需微调即可创建高质量对话代理的新方法&…