JAVA学习-多线程

线程

        线程(Thread)是一个程序内部的一条执行流程。

        程序中如果只有一条执行流程,那这个程序就是单线程的程序。

线程的常用方法及构造器:

Thread提供的常用方法public void run()
线程的任务方法public void start()
启动线程public String getName()
获取当前线程的名称,线程名称默认是Thread-索引public void setName(String name)
为线程设置名称public static Thread currentThread()
获取当前执行的线程对象public static void sleep(long time)
让当前执行的线程休眠多少毫秒后,再继续执行public final void join()...
让调用当前这个方法的线程先执行完!Thread提供的常见构造器public Thread(String name)
可以为当前线程指定名称public Thread(Runnable target)
封装Runnable对象成为线程对象public Thread(Runnable target, String name)
封装Runnable对象成为线程对象,并指定线程名称

多线程

        多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。

创建线程的方式

        多线程的创建方式一:继承Thread类

                操作步骤:

                        ① 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法

                        ② 创建MyThread类的对象

                        ③ 调用线程对象的start()方法启动线程(启动后还是执行run方法的)

                方式一优缺点:

                        优点:编码简单

                        缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。

        注意事项:

        直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。 只有调用start方法才是启动一个新的线程执行。

        不要把主线程任务放在启动子线程之前。 这样主线程是一直先跑完的,相当于是一个单线程的效果了。

/*** 方式一:继承Thread类*/
public class MyThread extends Thread{@Overridepublic void run() {for (int i = 1; i <= 20; i++) {System.out.println("新的线程执行:"+i);}}
}

/*** 创建线程方式一:继承Thread类* 1.创建一个继承Thread类的子类* 2.重写Thread类中的run方法,将此线程要执行的代码写在run方法中* 3.创建Thread类的子类的对象,调用start方法**/
public class ThreadDemo1 {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();// 启动线程(启动后还是执行run方法)  交替执行
//        myThread.run();//单线程for (int i = 1; i <= 20; i++) {System.out.println("主线程执行:"+i);}}}

        多线程的创建方式二:实现Runnable接口

                操作步骤:

                        ① 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法

                        ② 创建MyRunnable任务对象

                        ③ 把MyRunnable任务对象交给Thread处理。

                        ④ 调用线程对象的start()方法启动线程

                方式二的优缺点

                        优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。

                        缺点:需要多一个Runnable对象。

/*** 方式二: 实现Runnable接口*/
public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 1; i <= 20; i++) {System.out.println("MyRunable"+i);}}
}

public class RunnableDemo2 {public static void main(String[] args) {// 创建线程任务对象MyRunnable myRunnable = new MyRunnable();// 创建线程Thread thread = new Thread(myRunnable);// 启动线程thread.start();/***  使用匿名内部类, 创建线程*  写法* ① 可以创建Runnable的匿名内部类对象。* ② 再交给Thread线程对象。* ③ 再调用线程对象的start()启动线程。*/new Thread(new Runnable(){@Overridepublic void run() {for (int i = 1; i <= 20; i++) {System.out.println("匿名内部类1:"+i);}}}).start();// 使用Lambda表达式 简化new Thread(()-> {for (int i = 1; i <= 20; i++) {System.out.println("匿名内部类2:"+i);}}).start();for (int i = 1; i <= 20; i++) {System.out.println("main:"+i);}}}

        线程的创建方式三:实现Callable接口,利用FutureTask类来实现

                操作步骤:

                        ① 创建任务对象 ➢ 定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。 ➢ 把Callable类型的对象封装成FutureTask(线程任务对象)。

                        ② 把线程任务对象交给Thread对象。

                        ③调用Thread对象的start方法启动线程。

                        ④ 线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。

                线程创建方式三的优缺点

                        优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结 果。

                        缺点:编码复杂一点。

import java.util.concurrent.Callable;/*** 方式三:实现Callable接口*/
public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {for (int i = 1; i <= 20; i++) {System.out.println("约吗"+i);}return null;}
}

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** 创建线程的第三种方式:实现Callable接口* 优点:* 线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;* 可以在线程执行完毕后去获取线程执行的结果。**/
public class CallableDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建线程任务对象MyCallable myCallable = new MyCallable();// 创建FutureTask对象, 构造方法中传递线程任务对象FutureTask<String> ft = new FutureTask<>(myCallable);// 创建线程对象, 构造方法中传递线程对象Thread thread = new Thread(ft);// 启动线程thread.start();/**获取线程执行结果线程必须在启动之后,才能获取到线程执行结果。get()方法会阻塞当前线程,直到获取到线程执行结果。如果get()方法在获取到线程执行结果之前,当前线程被中断,那么会抛出InterruptedException异常。*/String s = ft.get();System.out.println(s);for (int i = 1; i <= 20; i++) {System.out.println("吃瓜"+i);}}}

什么是线程安全问题?

        多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。

线程安全卖票模拟:

public class Ticket implements Runnable{private int tivket=100;@Overridepublic void run() {while (true){if (tivket <=0){break;}else{try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}tivket--;System.out.println(Thread.currentThread().getName()+ "在卖票,剩余" + tivket + "张票");}}}}

        测试类:    

public class TicketDemo {public static void main(String[] args) {Ticket ticket = new Ticket();Thread thread1 = new Thread(ticket,"Thread1");Thread thread2 = new Thread(ticket,"Thread2");Thread thread3 = new Thread(ticket,"Thread3");thread1.start();thread2.start();thread3.start();}
}

   部分运行结果:

Thread1在卖票,剩余98张票
Thread2在卖票,剩余98张票
Thread3在卖票,剩余98张票
Thread1在卖票,剩余95张票
Thread3在卖票,剩余96张票
Thread2在卖票,剩余96张票
Thread3在卖票,剩余94张票
Thread1在卖票,剩余94张票
Thread2在卖票,剩余93张票
Thread3在卖票,剩余92张票
......
Thread1在卖票,剩余4张票
Thread3在卖票,剩余3张票
Thread2在卖票,剩余2张票
Thread1在卖票,剩余1张票
Thread3在卖票,剩余0张票
Thread2在卖票,剩余-1张票
Thread1在卖票,剩余-2张票

从结果可以看出,有重复卖票,还有票卖超了,这就是线程安全问题。

解决线程安全问题可以通过线程同步来解决。

线程同步

        线程同步的核心思想:让多个线程先后依次访问共享资源,这样就可以避免出现线程安全问题。

        解决方案:

        一:同步代码块

                作用:把访问共享资源的核心代码给上锁,以此保证线程安全。

synchronized(同步锁) {//出现线程安全问题的代码}// 对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。

        锁对象的使用规范

                 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象。

                对于静态方法建议使用字节码(类名.class)对象作为锁对象。

        二:同步方法

                作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

修饰符synchronized 返回值类型 方法名称(形参列表) 
{操作共享资源的代码
}

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行

        同步方法底层原理

                同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。

                如果方法是实例方法:同步方法默认用this作为的锁对象。

                如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

        同步代码块好还是同步方法好?

                范围上:同步代码块锁的范围更小,同步方法锁的范围更大

                可读性:同步方法更好

        三:Lock锁

                Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。

                Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。

public ReentrantLock​() 获得Lock锁的实现类对象void lock()            获得锁void unlock()          释放锁

线程池

        线程池就是一个可以复用线程的技术。

        不使用线程池,用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的, 创 建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。

        创建线程池:

方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)//使用指定的初始化参数创建一个新的线程池对象// 创建线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, // 线程池的核心线程数量10, // 线程池的最大线程数量60, // 临时线程存活时间TimeUnit.SECONDS, // 临时线程存活时间单位new ArrayBlockingQueue<>(20), // 任务队列Executors.defaultThreadFactory(), // 线程工厂new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);

        参数一:corePoolSize : 指定线程池的核心线程的数量。

        参数二:maximumPoolSize:指定线程池的最大线程数量。

        参数三:keepAliveTime :指定临时线程的存活时间。 正式工:3 最大员工数:5 临时工:2 临时工空闲多久被开除

        参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)

        参数五:workQueue:指定线程池的任务队列。 客人排队的地方

        参数六:threadFactory:指定线程池的线程工厂。 负责招聘员工的(hr)

        参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)

方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

poolExecutor.execute();
import java.util.List;
import java.util.concurrent.*;/*** void execute(Runnable command) 执行 Runnable 任务* Future<T> submit(Callable<T> task) 执行 Callable 任务,返回未来任务对象,用于获取线程返回的结果* void shutdown() 等全部任务执行完毕后,再关闭线程池!* List<Runnable> shutdownNow() 立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任*/
public class Demo2 {public static void main(String[] args) {// 创建线程池ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3,10,60,TimeUnit.HOURS,new ArrayBlockingQueue<>(20),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());for (int i = 1; i <= 16; i++) {/*** 创建线程* 新任务提交时发现核心线程都在忙,任务队列也满了,* 并且还可以创建临时线程,此时才会创建临时线程** 核心线程和临时线程都在忙,任务队列也满了(最大线程数10+队列数20)* 新的任务过来的时候才会开始拒绝任务。**/// 创建线程, 执行Runnable任务poolExecutor.execute(new Student("小红"+i));// 创建线程, 执行Callable任务poolExecutor.execute(new FutureTask<>(new Student2("张三"+i)));}// 关闭线程池poolExecutor.shutdown();}}class Student implements Runnable{private String name;public Student(String name){this.name=name;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"在教:"+name+"学游泳");}
}class Student2 implements Callable {private String name;public Student2(String name){this.name=name;}@Overridepublic Object call() throws Exception {System.out.println(Thread.currentThread().getName()+"在教:"+name+"学游泳");return null;}
}

任务拒绝策略:

ThreadPoolExecutor.AbortPolicy()
丢弃任务并抛出RejectedExecutionException异常。是默认的策略ThreadPoolExecutor. DiscardPolicy()
丢弃任务,但是不抛出异常,这是不推荐的做法ThreadPoolExecutor. DiscardOldestPolicy()
抛弃队列中等待最久的任务 然后把当前任务加入队列中ThreadPoolExecutor. CallerRunsPolicy()
由主线程负责调用任务的run()方法从而绕过线程池直接执行

并发/并行:

        并发的含义 :进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行, CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。

        并行的理解: 在同一个时刻上,同时有多个线程在被CPU调度执行。


#学无止尽    #记录并分享我的学习日常

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

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

相关文章

Github 2025-04-19Rust开源项目日报 Top10

根据Github Trendings的统计,今日(2025-04-19统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10Python项目1Rust: 构建可靠高效软件的开源项目 创建周期:5064 天开发语言:Rust协议类型:OtherStar数量:92978 个Fork数量:12000…

OpenLayers:视图变换的方法

一、什么是视图变换&#xff1f; 视图变换就是指视图的 extent&#xff08;范围&#xff09;、center&#xff08;中心点&#xff09;、zoom&#xff08;缩放级别&#xff09;、 resolution&#xff08;分辨率&#xff09;、rotation&#xff08;旋转角&#xff09;等参数发生…

数字孪生火星探测车,星际探索可视化

运用图扑构建数字孪生火星探测车&#xff0c;高精度还原外观与内部构造。实时映射探测车在火星表面的移动、探测作业及设备状态&#xff0c;助力科研人员远程监测、分析数据&#xff0c;为火星探索任务提供可视化决策支持。

【NLP 66、实践 ⑰ 基于Agent + Prompt Engineering文章阅读】

你用什么擦干我的眼泪 莎士比亚全集 工业纸巾 还是你同样泛红的眼睛 —— 4.19 一、⭐【核心函数】定义大模型调用函数 call_large_model prompt&#xff1a;用户传入的提示词&#xff08;如 “请分析这篇作文的主题”&#xff09;&#xff0c;指导模型执行任务 client&…

黑马Java基础笔记-1

JVM&#xff0c;JDK和JRE JDK是java的开发环境 JVM虚拟机&#xff1a;Java程序运行的地方 核心类库&#xff1a;Java已经写好的东西&#xff0c;我们可以直接用。 System.out.print中的这些方法就是核心库中的所包含的 开发工具: javac&#xff08;编译工具&#xff09;、java&…

PR第一课

目录 1.新建 2.PR内部设置 3.导入素材 4.关于素材窗口 5.关于编辑窗口 6.序列的创建 7.视频、图片、音乐 7.1 带有透明通道的素材 8.导出作品 8.1 打开方法 8.2 导出时&#xff0c;需要修改的参数 1.新建 2.PR内部设置 随意点开 编辑->首选项 中的任意内容&a…

Xcode16 调整 Provisioning Profiles 目录导致证书查不到

cronet demo 使用的 ninja 打包&#xff0c;查找 Provisioning Profiles 路径是 ~/Library/MobileDevice/Provisioning Profiles&#xff0c;但 Xcode16 把该路径改为了 ~/Library/Developer/Xcode/UserData/Provisioning Profiles&#xff0c;导致在编译 cronet 的demo 时找不…

【更新完毕】2025华中杯C题数学建模网络挑战赛思路代码文章教学数学建模思路:就业状态分析与预测

完整内容请看文末最后的推广群 先展示文章和代码、再给出四个问题详细的模型 基于多模型下的就业状态研究 摘要 随着全球经济一体化和信息技术的迅猛发展&#xff0c;失业问题和就业市场的匹配性问题愈加突出。为了解决这一问题&#xff0c;本文提出了一种基于统计学习和机器学…

[HOT 100] 1964. 找出到每个位置为止最长的有效障碍赛跑路线

文章目录 1. 题目链接2. 题目描述3. 题目示例4. 解题思路5. 题解代码6. 复杂度分析 1. 题目链接 1964. 找出到每个位置为止最长的有效障碍赛跑路线 - 力扣&#xff08;LeetCode&#xff09; 2. 题目描述 你打算构建一些障碍赛跑路线。给你一个 下标从 0 开始 的整数数组 obst…

2025年KBS SCI1区TOP:增强天鹰算法EBAO,深度解析+性能实测

目录 1.摘要2.天鹰算法AO原理3.改进策略4.结果展示5.参考文献6.代码获取 1.摘要 本文提出了增强二进制天鹰算法&#xff08;EBAO&#xff09;&#xff0c;针对无线传感器网络&#xff08;WSNs&#xff09;中的入侵检测系统&#xff08;IDSs&#xff09;。由于WSNs的特点是规模…

JavaScript数据类型简介

在JavaScript中&#xff0c;理解不同的数据类型是掌握这门语言的基础。数据类型决定了变量可以存储什么样的值以及这些值能够执行的操作。JavaScript支持多种数据类型&#xff0c;每种都有其特定的用途和特点。本文将详细介绍JavaScript中的主要数据类型&#xff0c;并提供一些…

性能比拼: Elixir vs Go(第二轮)

本内容是对知名性能评测博主 Anton Putra Elixir vs Go (Golang) Performance Benchmark (Round 2) 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准 这是第二轮关于 Elixir 和 Go 的对比测试。我收到了一份来自 Elixir 创作者的 Pull Request &#xff0c;并且我认为…

接口自动化 ——fixture allure

一.参数化实现数据驱动 上一篇介绍了参数化&#xff0c;这篇 说说用参数化实现数据驱动。在有很多测试用例的时候&#xff0c;可以将测试用例都存储在文件里&#xff0c;进行读写调用。本篇主要介绍 csv 文件和 json 文件。 1.读取 csv 文件数据 首先创建 csv 文件&#xff…

`peft`(Parameter-Efficient Fine-Tuning:高效微调)是什么

peft(Parameter-Efficient Fine-Tuning:高效微调)是什么 peft库是Hugging Face推出的用于高效参数微调的库,它能在不调整模型全部参数的情况下,以较少的可训练参数对预训练模型进行微调,从而显著降低计算资源需求和微调成本。以下从核心功能、优势、常见微调方法、使用场…

编程常见错误归类

上一篇讲了调试&#xff0c;今天通过一个举例回忆一下上一篇内容吧&#xff01; 1. 回顾&#xff1a;调试举例 在VS2022、X86、Debug的环境下&#xff0c;编译器不做任何优化的话&#xff0c;下⾯代码执⾏的结果是啥&#xff1f; #include <stdio.h> int main() {int …

在windows上交叉编译opencv供RK3588使用

环境 NDK r27、RK3588 安卓板子、Android 12 步骤操作要点1. NDK 下载选择 r27 版本&#xff0c;解压到无空格路径&#xff08;如 C:/ndk&#xff09;2. 环境变量配置添加 ANDROID_NDK_ROOT 和工具链路径到系统 PATH3. CMake 参数调整指定 ANDROID_NATIVE_API_LEVEL31、ANDRO…

浅析MySQL事务锁

在 MySQL 中,事务锁是用于确保数据一致性和并发控制的重要机制。事务锁可以帮助防止多个事务同时修改同一数据,从而避免数据不一致和脏读、不可重复读、幻读等问题。 以下是 MySQL 事务锁的关键点总结: 事务锁:用于确保数据一致性和并发控制。锁的类型: 行级锁:InnoDB,粒…

vue3 文件下载(excel/rar/zip)

安装axios npm install axios 在项目中引入 import axios from axios; 1、get接口excel文件下载 const file_key ref() const downLoadExcel (value:any) > {//file_key.value value axios({method: "get",url: "/api/da/download_excel/",//url:…

RT-Thread RTThread studio 初使用

RT-Thread Studio 下载 https://www.rt-thread.org/studio.html 安装使用 https://bbs.elecfans.com/jishu_2425653_1_1.html 4 编译问题解决 问题一&#xff1a;error: unknown type name clock_t 具体的类型值是在sys/_types.h中定义的&#xff0c;需要包含sys/_types.h 这个…

汉诺塔专题:P1760 通天之汉诺塔 题解 + Problem D: 汉诺塔 题解

1. P1760 通天之汉诺塔 题解 题目背景 直达通天路小A历险记第四篇 题目描述 在你的帮助下&#xff0c;小 A 成功收集到了宝贵的数据&#xff0c;他终于来到了传说中连接通天路的通天山。但是这距离通天路仍然有一段距离&#xff0c;但是小 A 突然发现他没有地图&#xff0…