Java——多线程

🏡个人主页:謬熙,欢迎各位大佬到访❤️❤️❤️~
👲个人简介:本人编程小白,正在学习互联网开发求职知识……
如果您觉得本文对您有帮助的话,记得点赞👍、收藏⭐️、评论💬,如果文章有什么需要改进的地方还请大佬不吝赐教🙏🙏🙏
在这里插入图片描述

目录

  • 操作系统中相关概念解释
    • 线程与进程
    • 并发与并行
  • 线程的生命周期和状态
  • Java 相关包
  • 多线程实现与调度
    • 直接继承`Thread`类(无返回值)
    • 实现`Runable`接口后,创建`Thread`对象(避免了单继承问题,无返回值)
    • 实现`Callable`接口和`Future`接口(有返回值)
    • 守护线程、礼让线程、插入线程
  • 线程同步
    • synchronized同步
    • lock锁
    • 死锁(一种错误,需要避免)
  • 等待唤醒机制
    • 阻塞队列方式
  • 线程池
    • Executors执行器自动创建线程池(不规范)
    • ThreadPoolExecutor自定义创建线程池

在这里插入图片描述

操作系统中相关概念解释

线程与进程

进程是程序的基本执行实体;(不同软件)
线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位。(同一软件不同功能)
线程与进程

并发与并行

并发:多个指令在同一个CPU上交替执行;
并行:同一时刻,多个指令在多个CPU上同时执行。(多核CPU)
在这里插入图片描述

线程的生命周期和状态

线程的生命周期分为创建(new)、就绪(Runnable)、运行(running)、阻塞(Blocked)、死亡(Dead)五种状态。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


Java 相关包

核心包中的线程类和接口
java.lang.Thread
java.lang.Runnable
JUC高并发编程包中的部分包
java.util.concurrent.Callable
java.util.concurrent.locks.Lock锁接口
java.util.concurrent.locks.ReentrantLock锁的实现类
java.util.concurrent.ArrayBlockingQueue有界阻塞队列
java.util.concurrent.LinkedBlockingQueue无界阻塞队列(int上限)
java.util.concurrent.Executors线程池


多线程实现与调度

在这里插入图片描述

直接继承Thread类(无返回值)

继承Thread类实现多线程有以下几步:

  1. 我们要先自定义一个类然后继承Thread类;
  2. 在继承Trread的类中重写run 方法;
  3. 通过创建该类的对象即可创建线程,创建多个对象就可以实现多线程;

Thread类常用成员方法:

  • start()开启线程
  • getname返回线程名称
  • setname设置线程名称
  • currentthread静态方法,获取并返回当前线程对象,无其他线程则返回默认主线程main
  • sleep静态方法,让当前进程休眠,单位毫秒
  • setPriority设置线程优先级,共有1-10个优先级,越小抢占CPU的概率越高
  • getPriority获取线程优先级,默认为5
package thread;public class MyThread extends Thread {@Overridepublic void run() {//书写线程要输出的方法for (int i = 0; i < 10; i++) {System.out.println(getName() + "hello");}}
}
package thread;public class ThreadDemo {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.setName("线程1");t2.setName("线程2");t1.start();t2.start();}
}

运行结果
结果

实现Runable接口后,创建Thread对象(避免了单继承问题,无返回值)

通过实现Runnable接口的方式实现多线程也大致可以分为以下几步

  1. 自定义一个了类实现Runnable接口;
  2. 重写run 方法;
  3. 创建自定义类的对象;
  4. 创建 Thread类对象,将自定义对象作为参数传递给 Thread对象;
  5. 通过调用 start 方法启动线程实现多线程;
package thread;public class MyRun implements Runnable {@Overridepublic void run() {Thread t = Thread.currentThread();for (int i = 0; i < 10; i++) {System.out.println(t.getName()+"hello");//这里由于不是继承自thread类,所以不能使用getname方法,所以先得获取一个线程对象}}
}
package thread;public class ThreadDemo2 {public static void main(String[] args) {MyRun mr = new MyRun();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);t1.setName("线程1");t1.start();t2.setName("线程2");t2.start();}
}

运行结果
结果

实现Callable接口和Future接口(有返回值)

通过实现Runnable接口的方式实现多线程也大致可以分为以下几步

  1. 创建一个自定义类实现 Callable 接口;
  2. 重写call ,(有返回值的,表示多线程的运行结果);
  3. 创建自定义类的对象,执行要执行的任务;
  4. 创建 Future 的对象,它可以管理多线程运行的结果,但是 Future 是一个接口,所以我们需要创建它的是实现类 FutureTask 的对象。
  5. 创建 Thread 的对象,并调用 start 启动线程。
package thread;import java.util.concurrent.Callable;public class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i < 10 ;i++){sum += i;}return sum;}
}
package thread;import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class ThreadDemo3 {public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable mc = new MyCallable();FutureTask<Integer> ft =new FutureTask<>(mc);Thread t1 = new Thread(ft);t1.start();System.out.println(ft.get());}
}

守护线程、礼让线程、插入线程

  • setDeamon()将线程设置为守护线程

作用是陪着非守护线程,当其结束时,守护线程也结束。
在这里插入图片描述
在这里插入图片描述

  • yield()静态方法,出让当前CPU的使用权
  • join()静态方法,将当前开启的线程插入到其他线程前

线程同步

synchronized同步

场景——电影院三个窗口同时出售100张票

package buyticket;public class Buy extends Thread{static int ticket = 0;public Buy(String name) {super(name);}@Overridepublic void run() {while(true){if(ticket<100){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(getName()+"已卖出第"+ticket+"张票!");}else{break;}}}
}

ticket不为静态变量引发的结果——三个窗口售票重复
在这里插入图片描述
安全问题

修改ticket后还是有问题——售出过多的票
在这里插入图片描述
原因:多个线程还在抢夺CPU,当线程1ticket自增到100后,还没来得及打印,线程2和线程3就相继苏醒也可能到了自增这一步操作,这时三个线程输出的值就变化了;
在这里插入图片描述

  • synchronized同步代码块
    当线程进入后,将代码自动锁起来,当里面的代码全部执行完毕后,锁打开其他线程才进入
package buyticket;public class Buy extends Thread {static int ticket = 0;public Buy(String name) {super(name);}@Overridepublic void run() {while (true) {synchronized (Buy.class) {if (ticket < 100) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(getName() + "已卖出第" + ticket + "张票!");} else {break;}}}}
}

细节1—— synchronized需要写在循环里面
在这里插入图片描述
否则一个线程线进入后会直接锁住这个循环体,当循环全部结束后才轮到其他线程。
在这里插入图片描述

细节2—— synchronized小括号里锁的对象必须唯一
如果锁对象不唯一,这个锁就没意义了。类似于抢厕所,锁对象唯一就是只能一个厕所轮流进,不唯一就是多个坑位,这个坑有人了我换一个呗。
一般用这个类的字节码文件对象类名.class,这个必唯一

  • synchronized同步方法
    将同步代码块中的核心方法提取出来,选中Ctrl+Alt+M,去掉同步代码块,给方法加上 synchronized关键字即可。
package buyticket;public class Buy_syn extends Thread {static int ticket = 0;public Buy_syn(String name) {super(name);}@Overridepublic void run() {while (true) {if (extracted()) {break;}}}private synchronized boolean extracted() {if (ticket < 100) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(getName() + "已卖出第" + ticket + "张票!");} else {return true;}return false;}
}

lock锁

  • 对象锁:在java中每个对象都有一个唯一的锁,对象锁用于对象实例方法或者一个对象实例上面的 —— 一个对象一把锁,100个对象100把锁。
  • 类锁:是用于一个类静态方法或者class对象的,一个类的实例对象可以有多个,但是只有一个class对象 —— 100个对象,也只是1把锁。上述synchronized同步代码块实现:在静态方法添加synchronized这把锁属于类了,所有这个类的对象都共享这把锁。
    在这里插入图片描述
package buyticket;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Buy_lock extends Thread {static int ticket = 0;static Lock lock = new ReentrantLock();//Lock 为接口,必须用实现类实例化public Buy_lock(String name) {super(name);}@Overridepublic void run() {while (true) {lock.lock();try {if (ticket < 100) {Thread.sleep(10);ticket++;System.out.println(getName() + "已卖出第" + ticket + "张票!");} else {break;}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}}
}

细节1——锁对象必须唯一,所以采用静态属性
细节2——如果直接break跳出循环,程序不会停止,所以必须关锁程序才结束运行。

死锁(一种错误,需要避免)

死锁是指在执行过程中,两个或两个以上的进程(或线程)由于竞争资源或彼此通信而阻塞,导致无法继续执行的情况。

直观例子:
老板:你给我好好干,我就给你加薪
员工:你给我加薪,我就好好干


等待唤醒机制

生产者消费者模型是一种经典的多线程同步模型,用于解决生产者和消费者之间的协作问题。在这个模型中,生产者负责生产数据并将其放入缓冲区(共享锁对象,也就是共享资源),消费者负责从缓冲区中取出数据并进行处理。生产者和消费者之间通过缓冲区进行通信,彼此之间不需要直接交互。这样可以降低生产者和消费者之间的耦合度,提高系统的可维护性和可扩展性。

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

场景——顾客吃饭,厨师做饭,缓冲区就是订单

职能flag = 0flag = 1
缓冲区没订单有订单
生产者wait等待下单操作处理订单,然后notify唤醒消费者
消费者下单,然后notify唤醒生产者wait等待吃完

缓冲区:

package wait;public class Desk {public static int flag = 0;public static int count = 0;public static Object lock = new Object();
}

生产者:

package wait;public class Cooker extends Thread{public Cooker(String name) {super(name);}public void run() {while (true) {synchronized (Desk.lock) {if (Desk.count == 3) {break;} else {if (Desk.flag == 0) {try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}} else {System.out.println("厨师已收到订单,做了一碗面条");Desk.flag = 0;Desk.lock.notifyAll();}}}}}
}

消费者:

package wait;public class Customer extends Thread {public Customer(String name) {super(name);}@Overridepublic void run() {while(true){synchronized (Desk.lock) {if (Desk.count == 3) {break;} else {if (Desk.flag == 1) {try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}} else {Desk.count++;System.out.println("客人正在吃" + Desk.count + "面条,已下一个新订单");Desk.flag = 1;Desk.lock.notifyAll();}}}}}
}

运行结果:
两个线程协调运作
在这里插入图片描述

阻塞队列方式

在这里插入图片描述

阻塞队列是一种特殊的队列,同样遵循“先进先出”的原则,支持入队操作和出队操作。在此基础上,阻塞队列会在队列已满或队列为空时陷入阻塞,使其成为一个线程安全的数据结构,它具有如下特性:

  • 当队列已满时,继续入队列就会阻塞,直到有其他线程从队列中取走元素take()
  • 当队列为空时,继续出队列也会阻塞,直到有其他线程向队列中插入元素put

主程序:

package waitblock;import java.util.concurrent.ArrayBlockingQueue;public class Main {public static void main(String[] args) {ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(1);Cooker cooker = new Cooker(queue);Customer customer = new Customer(queue);cooker.start();customer.start();}
}

消费者:

package waitblock;import java.util.concurrent.ArrayBlockingQueue;public class Customer extends Thread {ArrayBlockingQueue<String> queue;public Customer(ArrayBlockingQueue queue) {this.queue = queue;}@Overridepublic void run() {while (true) {try {String food = queue.take();System.out.println("顾客已收到食物——"+food);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

生产者:

package waitblock;import java.util.concurrent.ArrayBlockingQueue;public class Cooker extends Thread {ArrayBlockingQueue<String> queue;public Cooker(ArrayBlockingQueue queue) {this.queue = queue;}@Overridepublic void run() {while (true) {try {queue.put("面条");System.out.println("厨师已做完一碗面条");} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

细节——生产者和消费者的队列必须一致,所以在主程序实例化了一个阻塞队列对象,在两个类中利用构造函数实例化,这样队列就共享了。


线程池

在这里插入图片描述
核心原理

Executors执行器自动创建线程池(不规范)

Executors相当于执行器的工厂类,包含各种常用执行器的静态工厂方法,可以直接创建常用的执行器。几种常用的执行器如下:

  • Executors.newCachedThreadPool,根据需要可以创建新线程的线程池。线程池中曾经创建的线程,在完成某个任务后也许会被用来完成另外一项任务。
  • Executors.newFixedThreadPool(int nThreads) ,创建一个可重用固定线程数的线程池。这个线程池里最多包含nThread个线程。起到限制并发线程数的作用。

下面是创建定长线程池(FixedThreadPool)的一个例子,严格来说,当使用如下代码创建线程池时,是不符合编程规范的。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

原因在于:(摘自阿里编码规约)
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
  主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM(Out Of Memory,来源于java.lang.OutOfMemoryError。当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error)
2)newCachedThreadPool和newScheduledThreadPool:
  主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

ThreadPoolExecutor自定义创建线程池

在这里插入图片描述

七个核心参数:

  • int corePoolSize,//核心线程数,线程池中始终存活的线程数
  • int maximumPoolSize,//最大线程数,当线程池的任务队列满了之后可以创建的最大线程数
  • long keepAliveTime,//空闲线程最大存货时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程
  • TimeUnit unit,//时间单位,TimeUnit.SECONDS设置秒
  • BlockingQueue workQueue,//任务队列,一个阻塞队列
  • ThreadFactory threadFactory,//创建线程工厂,创建线程
  • RejectedExecutionHandler handler//拒绝策略,拒绝处理任务时的策略,默认策略为AbortPolicy:拒绝并抛出异常。
    在这里插入图片描述

本次关于Java多线程的内容就总结到这里了,上述内容只是一个大纲,每个部分细节其实还蛮多的,后续会慢慢完善✏️。有疑问的地方或者纰漏的地方欢迎大佬们沟通指正,希望和大家一起进步~💪💪💪

在这里插入图片描述

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

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

相关文章

HCIP --OSI七层参考模型回顾、TCP/UDP协议复习

目录 一、OSI 二、重要的三个协议报头格式 名词注解 MTU 封装 解封装 PDU ARP DNS TCP/IP与OSI的区别 三、数据包转发过程 四、获取目标ip地址方式 五、获取目标mac地址方式 六、交换机的工作原理 七、TCP/UDP TCP&#xff08;Transmission Control Protocol&a…

基于Java Springboot甘肃旅游管理系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

基于YOLOv8深度学习的独居老人情感状态监护系统(PyQt5界面+数据集+训练代码)

本研究提出了一种创新的独居老人情感状态监护系统&#xff0c;基于YOLOV8深度学习模型&#xff0c;旨在通过对老年人面部表情的实时监测与分析&#xff0c;来精准识别其情感变化&#xff0c;从而提高独居老人的生活质量&#xff0c;确保其心理健康。本系统通过整合先进的YOLOV8…

博客文章怎么设计分类与标签

首发地址&#xff08;欢迎大家访问&#xff09;&#xff1a;博客文章怎么设计分类与标签 新网站基本上算是迁移完了&#xff0c;迁移之后在写文章的过程中&#xff0c;发现个人的文章分类和标签做的太混乱了&#xff0c;分类做的像标签&#xff0c;标签也不是特别的丰富&#x…

1+X应急响应(网络)网络流量分析技术:

网络流量分析技术&#xff1a; 全流量分析&#xff1a; Wireshark常用功能&#xff1a; Wireshark界面&#xff1a; Wireshark常用的使用方法&#xff1a; 全流量分析-分析web攻击&#xff1a; 全流量分析-分析DNS攻击&#xff1a; 全流量分析-分析病毒&#xff1a; 全流量分析…

LLM评测指标与评测方法

文章目录 常见评测指标语言建模任务相关评测指标分类任务相关评测指标条件文本生成任务相关评测指标问答任务相关评测指标执行类任务相关评测指标偏好排序任务相关评测指标随着大模型技术研究的快速发展,学术界和工业界相继发布了众多大语言模型。这些模型有的展现出强大的通用…

面向服务的软件工程——巨详细讲解商务流程建模符号 (BPMN),一篇章带你入门BPMN!!!(week1)

文章目录 一、前言二、重点概念三、BPMN元素讲解流对象1.活动任务(Task)子流程(sub-process)多实例活动连接对象序列流消息流关联泳道Artifacts数据对象组(Group)事件(Events)启动事件中间事件结束事件边界事件边界事件1边界事件2小疑问?网关参考文献:一、前言 在我们…

机器翻译-基础与模型

一、机器翻译发展历程 基于规则的-->基于实例的-->基于统计方法的-->基于神经网络的 传统统计机器翻译把词序列看作离散空间里的由多个特征函数描述的点&#xff0c;类似 于 n-gram 语言模型&#xff0c;这类模型对数据稀疏问题非常敏感。神经机器翻译把文字序列表示…

thinkphp6模板调用URL方法生成的链接异常

var uul params.url ;console.log(params.url);console.log("{:Url(UserLog/index)}");console.log("{:Url("uul")}"); 生成的链接地址 UserLog/index /jjg/index.php/Home/UserLog/index.html /jjg/index.php/Home/Index/UserLog/index.html…

基于Java Springboot网上花卉购物系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

WQ9101 WIFI6模组移植实操

物奇WQ9101是集成 Wi-Fi和蓝牙子系统的双模芯片。支持 802.11a/b/g/n/ac/ax协议、2.4G5G双频并发的 Wi-Fi子系统&#xff0c;以及支持Bluetooth 5.3协议的蓝牙子系统。主要应用于 电视、 平板电视、 平板PC、手机智能音箱等 、手机智能音箱等 、手机智能音箱等领域 。 一、平台…

领海基点的重要性-以黄岩岛(民主礁)的领海及专属经济区时空构建为例

目录 前言 一、Turf.js缓冲区绘制 1、缓冲区分析介绍 2、缓冲区参数 3、Mask多边形空洞 4、Mask参数 二、领海基点、领海的WebGIS展示 1、领海基点的绘制 2、领海面的绘制 三、毗邻区和专属经济区绘制 1、毗邻区的绘制 2、专属经济区的绘制 四、总结 前言 在上一篇的…

【Pythonr入门第二讲】你好,世界

"Hello, World!" 是一种传统的编程入门示例&#xff0c;通常是程序员学习一门新编程语言时编写的第一个程序。这个程序的目标非常简单&#xff1a;在屏幕上输出 "Hello, World!" 这个字符串。尽管它非常简单&#xff0c;但具有重要的象征意义和实际价值。 …

25.UE5时间膨胀,慢动作,切换地图,刷BOSS

2-27 时间膨胀、慢动作、切换地图、刷BOSS_哔哩哔哩_bilibili 目录 1.刷新BOSS逻辑 2.时间膨胀实现慢动作 3.胜利画面&#xff0c;下一关 3.1胜利画面UI 3.2第一关、第二关游戏模式 3.3下一关按钮事件的绑定 1.刷新BOSS逻辑 实现当场上的怪物都死亡后&#xff0c;进行刷…

探索Python PDF处理的奥秘:pdfrw库揭秘

文章目录 探索Python PDF处理的奥秘&#xff1a;pdfrw库揭秘1. 背景&#xff1a;为何选择pdfrw&#xff1f;2. pdfrw是什么&#xff1f;3. 如何安装pdfrw&#xff1f;4. 五个简单的库函数使用方法4.1 读取PDF信息4.2 修改PDF元数据4.3 旋转PDF页面4.4 提取PDF中的图片4.5 合并P…

游戏引擎学习第19天

介绍 这段内容描述了开发者在进行游戏开发时&#xff0c;对于音频同步和平台层的理解和调整的过程。以下是更详细的复述&#xff1a; 开发者表达了他希望今天继续进行的工作内容。他提到&#xff0c;昨天他讲解了一些关于音频的内容&#xff0c;今天他想稍微深入讲解一下他正…

【初阶数据结构与算法】线性表之栈和队列的定义与实现(含源码和有效的括号练习)

文章目录 一、栈的概念与结构1.栈的概念与操作2.栈的底层结构选型 二、栈的实现1.栈结构的定义2. 栈的初始化和销毁栈的初始化栈的销毁 3.栈的扩容与入栈栈的扩容入栈 4.判断栈是否为空和出栈判断栈是否为空出栈 5.取栈顶元素和获取栈中有效元素个数取栈顶元素获取栈中有效元素…

详细分析ipvsadm负载均衡的命令

目录 前言1. 基本知识2. 命令参数3. 拓展 前言 LVS四层负载均衡架构详解Lvs推荐阅读&#xff1a;添加链接描述 1. 基本知识 ipvsadm 是用于管理和配置 Linux 服务器上 IP Virtual Server (IPVS) 的工具&#xff0c;是 Linux 提供的一个负载均衡模块&#xff0c;支持多种负载…

小程序-基于java+SpringBoot+Vue的小区服务管理系统设计与实现

项目运行 1.运行环境&#xff1a;最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 2.IDE环境&#xff1a;IDEA&#xff0c;Eclipse,Myeclipse都可以。推荐IDEA; 3.tomcat环境&#xff1a;Tomcat 7.x,8.x,9.x版本均可 4.硬件环境&#xff1a…

苹果ASA归因对接以及API接入

一、归因概要 广告归因&#xff0c;目的是用于衡量广告带来的激活用户的成本以及后续进一步的用户质量表现。 Apple Ads 广告平台是基于 App Store&#xff08;站内广告&#xff09;&#xff0c;同时属于自归因平台&#xff08;通常称为 SAN&#xff09;。这两个因素&#xff…