要想搞懂多线程,大家心里肯定会有疑问,多线程是什么,为什么要用多线程,然后怎么用呢?
1.多线程是什么
多线程是指在一个进程中可以同时存在多个执行路径,这些执行路径就是线程。一个进程可包含多个线程, 线程是进程的细分,是程序内部的一条执行路径。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC),但同一进程中的多个线程共享相同的内存单元 / 内存地址空间,它们可以访问相同的变量和对象。
(1)计算机中程序、进程、线程与管程的基本概念
- 程序(program):是完成特定任务、用特定语言编写的指令集合 ,是静态代码与静态对象,像存于电脑里的 QQ、音乐播放软件安装包这类未运行的代码,就属于程序。
- 进程(process):是程序的一次执行过程,是动态的,有自身产生、存在和消亡的生命周期,且是系统资源分配的单位,系统会为每个进程分配不同内存区域,比如正在运行的 QQ、正在播放音乐的音乐播放器,就是进程在活动。
- 线程(thread):是进程细化后的执行路径,若进程同一时间并行执行多个线程,就是多线程。线程有独立运行栈和程序计数器,切换开销小,同一进程里多个线程共享内存单元,通信便捷高效,但操作共享资源易有安全隐患,例如音乐播放器进程里,播放音乐、显示界面、下载歌词等可分别由不同线程负责 。
- 管程(Monitor):是管理共享资源并发访问的高级同步机制,是包含共享数据、操作这些数据的过程及同步条件的软件模块,是一种静态的程序结构。像用于协调生产者和消费者访问共享缓冲区的代码模块,就属于管程。
对于程序相信大家都很熟悉了,不再进行过多介绍。下面来重点介绍进程、线程以及管程:
①进程:
- 本质与功能:程序本身是由指令和数据构成的静态实体 。当要运行程序中的指令、读写数据时,就需要把指令加载到 CPU,数据加载到内存,在运行过程中还会涉及磁盘、网络等设备的使用。进程的作用就在于负责加载指令、管理内存以及管理输入输出(I/O)操作。简单来说,进程是程序的动态执行过程,它为程序的运行提供了必要的资源和环境。
- 创建过程:当运行一个程序时,系统会从磁盘将该程序的代码读取并加载到内存中,这时就创建了一个进程。例如,当你双击打开计算机中的记事本应用程序,操作系统会在后台创建一个记事本进程,把记事本程序的相关代码和数据加载到内存,并分配相应的系统资源(如内存空间、文件句柄等) ,以便记事本程序能够正常运行。
- 实例情况:
- 大部分程序可以同时运行多个实例。以浏览器为例,你可以同时打开多个浏览器窗口,每个窗口对应一个浏览器进程,它们相互独立,有各自的内存空间和系统资源。同样,记事本、画图工具也能多次打开,每次打开都会创建一个新的进程。
- 有些程序只能启动一个实例进程。像网易云音乐,当你已经打开一个网易云音乐程序后,再次尝试打开它,系统不会创建新的进程,而是将已经打开的程序界面显示出来。这是因为程序在设计时设置了单例模式等机制,避免多个进程同时运行造成资源冲突或其他问题 。
②线程:
- 与进程的关系:一个进程内部可以划分为一个或多个线程。进程像是一个容器,线程则是在这个容器中实际执行任务的工作单元。例如在一个音乐播放软件进程中,会有负责播放音乐的线程、负责显示播放界面的线程、负责网络歌曲下载的线程等。
- 执行方式:线程可以理解为一个指令流,它按照一定顺序将指令流中的一条条指令交给 CPU 执行。例如在一个简单的多线程文本处理程序中,一个线程负责读取文本文件内容,另一个线程负责对读取到的内容进行关键词搜索。每个线程都有自己独立的执行路径和运行栈,但它们共享所属进程的内存空间等资源。
- 不同平台下的角色定位:
- 在 Java 中,线程是最小的调度单位,这意味着操作系统会根据线程的优先级等因素来决定先执行哪个线程。而进程则是资源分配的最小单位,Java 程序运行时,进程会获取如内存、文件描述符等资源,线程在执行过程中使用这些由进程分配的资源 。
- 在 Windows 系统中,进程主要作为线程的容器存在,本身并不活跃。Windows 操作系统主要对线程进行调度,决定线程在 CPU 上的执行顺序。线程在进程提供的资源环境中执行任务,比如多个线程可以共享进程所拥有的内存区域,进行数据的读写操作。
③管程:
- 本质:封装共享数据、操作方法、同步条件(如等待队列),作为 “资源管理中心”,让线程通过统一接口安全访问共享资源,避免直接操作数据引发的并发问题。
- 核心特点:
- 数据封装:共享数据 + 操作函数绑定,外部只能通过管程接口访问,隔离复杂度(类似 “黑盒”,用户不用关心内部同步细节)。
- 互斥访问:管程自动保证 “同一时间仅一个线程执行内部方法”(靠内置锁实现),天然解决多线程竞争共享资源的互斥问题。
- 条件变量:支持线程因资源不足主动等待(wait),条件满足时被其他线程唤醒(signal),灵活协调线程协作(比如生产者 - 消费者里,“缓冲区满 / 空” 的状态联动)。
- 实际价值:
- 解决多线程同步:典型场景是生产者 - 消费者、读者 - 写者等问题,替代低级锁(如互斥量),更安全、易用(减少死锁风险,代码逻辑更清晰)。
- 设计思想:用 “封装 + 自动化同步” 降低并发编程难度,让开发者聚焦业务逻辑,不用手动处理复杂的锁竞争、线程调度。
2.为什么要用多线程
了解了什么是多线程以后,我们也要明白为什么要用多线程。
(1)多线程的意义与价值
- 提高应用程序的响应:对于图形化界面程序来说意义重大。比如在一个图形化的文本编辑器中,主线程负责界面的显示和更新,而可以开启一个或多个子线程来处理文件的保存操作。这样在保存文件的过程中,用户依然可以流畅地操作界面,不会出现界面卡死的情况,增强了用户体验。
- 提高计算机系统 CPU 的利用率:在单核 CPU 中,虽然同一时刻 CPU 只能执行一个线程,但由于线程切换的开销相对较小,多线程程序可以让 CPU 在不同线程之间快速切换执行。例如,一个线程在等待 I/O 操作(如读取文件数据、网络数据传输等)完成时,CPU 可以切换去执行其他线程的任务,而不是处于空闲等待状态,从而提高了 CPU 的利用率。在多核 CPU 环境下,多线程可以真正并行执行,充分发挥多核的性能优势。
- 改善程序结构:将既长又复杂的进程拆分为多个线程,让每个线程独立运行,便于程序的理解和修改。比如在一个网络爬虫程序中,可以分别设置线程来负责网页链接的获取、网页内容的下载、数据解析等任务,每个线程专注于一项特定功能,使得程序结构更加清晰。
(2)多线程的使用场景
①高并发服务器
网络服务器(如Web服务器、游戏服务器)需要同时处理多个客户端请求。通过多线程,每个客户端连接可以由独立的线程处理,避免阻塞主线程,提高吞吐量。例如:
- HTTP服务器:每个请求分配一个线程处理。
- 数据库连接池:线程池管理多个数据库查询任务。
②计算密集型任务
需要大量CPU运算的任务(如科学计算、图像处理)可以通过多线程拆分到多个CPU核心上并行执行,缩短总耗时。例如:
- 矩阵运算:将大规模矩阵分块并行计算。
- 视频编码:多线程分段处理视频帧。
③异步I/O操作
当程序需要等待磁盘读写、网络通信等I/O操作时,单线程会因阻塞而浪费CPU资源。多线程可以将I/O操作放到后台线程执行,主线程保持响应。例如:
- 文件批量处理:后台线程压缩文件,主线程更新进度条。
- 爬虫程序:多个线程同时下载不同网页。
④用户界面响应
图形界面程序(GUI)需要保持主线程的流畅交互,耗时任务(如数据加载)应放到子线程中执行。例如:
- 桌面应用:子线程加载数据,主线程实时渲染界面。
- 游戏开发:后台线程加载资源,避免卡顿。
⑤定时任务调度
需要周期性执行的任务(如日志清理、缓存刷新)可以通过多线程定时触发,而无需阻塞主流程。例如:
- 监控系统:多个线程独立采集不同指标数据。
- 消息队列消费:多线程并发处理队列中的消息。
⑥实时数据处理
对实时性要求高的场景(如金融交易、传感器数据流)可通过多线程实现数据采集与分析并行。例如:
- 高频交易系统:一个线程接收市场数据,另一个线程执行策略计算。
- 物联网设备:多线程分别处理传感器数据和网络通信。
3.多线程的创建
下面介绍四种常见的多线程创建模式:
(1)继承Thread类
🐾步骤总结:
- 自定义类继承 Thread 类
- 覆写 Thread 类的 run () 方法,将多线程需并发执行的代码写入其中
- 通过 new 创建自定义线程对象
- 调用线程对象的 start () 方法启动线程
注意事项:
- 不可直接调用 run () 方法执行线程
- 同一个线程对象的 start () 方法只能调用一次!!!
- 使用继承方式时需特别注意共享资源的处理问题
Java 的 JVM 支持多线程,由 java.lang.Thread 类体现线程,每个线程靠 Thread 对象的 run() 执行任务,启动线程需用 start()(而非直接调 run())。
Thread 类关键内容:
- 线程体:线程执行的核心逻辑写在
run()里,run()是线程的 “任务代码”。- 启动规则:必须用
start()启动线程(会触发 JVM 调用run(),并分配系统资源),不能直接调用run()(否则只是普通方法调用,不启动新线程)。- 构造方法(创建
Thread对象的方式):
Thread():无参构造,直接建线程对象。Thread(String threadname):指定线程名,方便调试 / 区分。Thread(Runnable target):传入实现Runnable的对象,把任务逻辑与线程类解耦(后续常用 “实现Runnable” 的方式)。Thread(Runnable target, String name):结合 “目标任务对象 + 线程名”,灵活创建。
示例代码1️⃣(简单模拟汽车站卖票):
要注意资源共享问题:利用static解决(用static来修饰ticket,以此来保证四个车站卖的是同一部分票!),或者提取一个类来private作为资源类来传递。
// 创建一个火车票的类
class TrainTicket{
private int ticket=100;
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
}
// 创建一个火车票的thread类(继承thread类)
class TicketThread extends Thread{
private static int ticket=100; // 确保四个线程,卖的是同一部分票
private TrainTicket trainTicket;
public TicketThread(String name){
super(name);
}
public TicketThread(String name,TrainTicket trainTicket){
super(name);
this.trainTicket=trainTicket;
}
@Override
public void run() {
while(true){
if(trainTicket.getTicket()>0){
trainTicket.setTicket(trainTicket.getTicket()-1);
System.out.println(this.getName()+"卖出了一张票,目前还剩["+trainTicket.getTicket()+"]张票!");
}else{
System.out.println("票已售完!");
break;
}
}
}
}
// 测试类
public class MyThreadDemo01 {
public static void main(String[] args) {
TrainTicket trainTicket=new TrainTicket();
TicketThread ticketThread1=new TicketThread("A站",trainTicket);
TicketThread ticketThread2=new TicketThread("B站",trainTicket);
TicketThread ticketThread3=new TicketThread("C站",trainTicket);
TicketThread ticketThread4=new TicketThread("D站",trainTicket);
ticketThread1.start();
ticketThread2.start();
ticketThread3.start();
ticketThread4.start();
}
}
运行结果:
A站卖出了一张票,目前还剩[97]张票!
D站卖出了一张票,目前还剩[96]张票!
C站卖出了一张票,目前还剩[98]张票!
-------------------------------------------------
C站卖出了一张票,目前还剩[8]张票!
A站卖出了一张票,目前还剩[3]张票!
B站卖出了一张票,目前还剩[4]张票!
D站卖出了一张票,目前还剩[5]张票!
B站卖出了一张票,目前还剩[0]张票!
票已售完!
A站卖出了一张票,目前还剩[1]张票!
票已售完!
C站卖出了一张票,目前还剩[2]张票!
票已售完!
票已售完!
可以发现,几个线程的运行是没有规律的,也就是说每个线程的执行都是随机的,谁抢到了就是谁的。还没来得及输出可能另一个线程就获得了CPU的执行权,所以会导致这个顺序是随机的。最后一张票有可能被四个线程都看到了,就会导致最后一张票被四个线程都获取了,这就涉及到了线程安全问题。
示例代码2️⃣(简单的多线程下载图片):
代码中-----------,替换成要下载的图片链接即可;下载到电脑的位置:d:\\images,可以自定义。
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Accessors(chain = true)
class ImageSrc{
private String urlPath;
private String fileName;
}
class WebImageDownlaodThread extends Thread{
private ImageSrc imageSrc;
public WebImageDownlaodThread(ImageSrc imageSrc){
this.imageSrc=imageSrc;
}
public void run(){
try {
FileUtils.copyURLToFile(
new URL(imageSrc.getUrlPath()),
new File("d:\\images",imageSrc.getFileName()));
System.out.println(Thread.currentThread().getName()+"下载图片完成");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public class WebImageDownLoadDemo {
public static void main(String[] args) {
Set<ImageSrc> set=new HashSet<>();
set.add(new ImageSrc("-----------","1.jpg"));
set.add(new ImageSrc("-----------","2.jpg"));
set.add(new ImageSrc("-----------","3.jpg"));
set.add(new ImageSrc("-----------","4.jpg"));
set.add(new ImageSrc("-----------","5.jpg"));
set.add(new ImageSrc("-----------","6.png"));
set.forEach(
imageSrc -> new WebImageDownlaodThread(imageSrc).start());
}
}
(2)利用Runnable接口
🐾步骤总结:
- 创建自定义实现Runnable接口的对象
- 创建Thread类,并把第一步创建出的实现Runnable接口的对象作为构造方法参数传入
- 启动线程(start())
注意事项:
要学会使用拉姆达表达式:
new Thread( ()->{里面写循环体},
name:“名称”).start(); //一边创建一边启动的写法
示例代码1️⃣(仍然是简单模拟汽车站卖票):
class TicketRunnable implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true) {
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"卖出了一张票,当前还剩["+(--ticket)+"]张票");
}else{
System.out.println(Thread.currentThread().getName()+"网点的票已售完!"+this.ticket);
break;
}
}
}
}
public class RunnableThreadDemo01 {
public static void main(String[] args) {
// 创建自定义实现Runnable接口的对象
TicketRunnable ticketRunnable=new TicketRunnable();
// 创建Thread类,并把第一步创建出的实现Runnable接口的对象作为构造方法参数传递进来
Thread t1=new Thread(ticketRunnable,"A站");
t1.start();
Thread t2=new Thread(ticketRunnable,"B站");
t2.start();
Thread t3=new Thread(ticketRunnable,"C站");
t3.start();
Thread t4=new Thread(ticketRunnable,"D站");
t4.start();
}
}
示例代码2️⃣(展示多线程的并发):
创建并启动一个自定义线程,同时在主线程中也执行一段循环打印操作,用于展示多线程的并发执行特性。
public class RunnableThreadDemo02 {
public static void main(String[] args) {
new Thread(()->{
for(int i=1;i<=1000;i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
}
},"自定义线程-1").start();
for(int i=1;i<=1000;i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
运行结果:
自定义线程-1-826
自定义线程-1-827
自定义线程-1-828
main-946
main-947
main-948
自定义线程-1-829
自定义线程-1-830
自定义线程-1-831
自定义线程-1-832
自定义线程-1-833
自定义线程-1-834
由于多线程的并发执行特性,“自定义线程 - 1” 和主线程会交替执行各自的循环打印操作,最终控制台会交替输出来自这两个线程的打印内容,我们可以清楚地看到多线程程序中多个线程同时(或交替)执行任务的特点。
示例代码3️⃣(简单模拟银行的存取款操作):
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
class BankAccount{
private String bankName;
private String account;
private int balance;
public void withDraw(int money){
System.out.println(Thread.currentThread().getName()+"对账号["+account+"]进行了存款操作");
this.balance=this.getBalance()+money;
System.out.println(Thread.currentThread().getName()+"存款操作成功!当前余额:"+this.getBalance());
}
}
public class RunnableThreadDemo03 {
public static void main(String[] args) {
BankAccount bankAccount=new BankAccount("交通银行","666666",0);
for(int i=1;i<=5;i++){
new Thread(()->{
bankAccount.withDraw(1000);
},"交通银行网点-"+i).start();
}
}
}
运行结果:
交通银行网点-5对账号[666666]进行了存款操作
交通银行网点-3对账号[666666]进行了存款操作
交通银行网点-1对账号[666666]进行了存款操作
交通银行网点-4对账号[666666]进行了存款操作
交通银行网点-2对账号[666666]进行了存款操作
交通银行网点-3存款操作成功!当前余额:2000
交通银行网点-2存款操作成功!当前余额:5000
交通银行网点-1存款操作成功!当前余额:3000
交通银行网点-4存款操作成功!当前余额:4000
交通银行网点-5存款操作成功!当前余额:1000
示例代码4️⃣(简单模拟同学们的状态):
public class RunnableThreadDemo04 {
public static void main(String[] args) {
new Thread(()->{
for(int i=1;i<10;i++){
System.out.println(Thread.currentThread().getName()+"正在写第-"+i+"-个程序 的代码");
}
},"张三").start();
new Thread(()->{
for(int i=1;i<10;i++){
System.out.println(Thread.currentThread().getName()+"正在打游戏,目前已经通关"+i);
}
},"李四").start();
}
}
运行结果:
张三正在写第-1-个程序 的代码
李四正在打游戏,目前已经通关1
李四正在打游戏,目前已经通关2
李四正在打游戏,目前已经通关3
李四正在打游戏,目前已经通关4
张三正在写第-2-个程序 的代码
李四正在打游戏,目前已经通关5
李四正在打游戏,目前已经通关6
张三正在写第-3-个程序 的代码
李四正在打游戏,目前已经通关7
李四正在打游戏,目前已经通关8
张三正在写第-4-个程序 的代码
李四正在打游戏,目前已经通关9
张三正在写第-5-个程序 的代码
张三正在写第-6-个程序 的代码
张三正在写第-7-个程序 的代码
张三正在写第-8-个程序 的代码
张三正在写第-9-个程序 的代码
示例代码5️⃣(创建奇偶线程):
public class RunnableThreadDemo05 {
public static void main(String[] args) {
new Thread(()->{
for(int i=1;i<=100;i+=2){
System.out.println(Thread.currentThread().getName()+"-"+i+"-");
}
},"奇数线程").start();
new Thread(()->{
for(int i=0;i<=100;i+=2){
System.out.println(Thread.currentThread().getName()+"-"+i+"-");
}
},"偶数线程").start();
}
}
运行结果(部分):
偶数线程-98-
奇数线程-93-
偶数线程-100-
奇数线程-95-
奇数线程-97-
奇数线程-99-
方式一和方式二(继承方式和实现方式)的联系与区别:
public class Thread extends Object implements Runnable
●区别
>继承Thread:线程代码存放Thread子类run方法中。
>实现Runnable:线程代码存在接口的子类的run方法。
●实现方式的好处
>避免了单继承的局限性
>多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
(3)利用Callable接口
功能更强大,可以有返回值,支持泛型,并且可以抛出异常!
🐾步骤总结:
涉及到类:Callable<V></V>接口,public V call(){},在juc包中
FutureTask<V> implements Runnable,Future<V>
Runnable,Thread
编程步骤:
1.编写一个自定义类,让自定义类实现Callable接口,并覆写该接口中的call()方法
2.创建一个FutureTask类型的对象,并把第一步自定义实现Callable接口的对象作为构造方法参数传递进来
3.创建一个Thread类型的对象,并把第2步中的FutureTask类型的对象作为参数传递进来
4.调用start()方法类启动线程
重要内容:
(1)其中用到的接口和重要的方法如下:
FutureTask:参数是Callable接口
Future 接口的方法:
(2)FutureTask、Future和Callable之间的关系:
- Callable:定义任务,有返回值。
- Future:代表任务的“未来结果”,可以用get()拿到。
FutureTask:桥梁,既是Runnable(能被线程执行),又是Future(能拿结果),内部包装了Callable。
关系图如下:
(3)程序结构:
* FutureTask ft=new FutureTask(new Callable<T>(){
* public T call(){* }
* });
* Thread t=new Thread(ft)
*/
代码示例1️⃣:
通过 Callable、FutureTask 和 Thread 的结合使用,实现了一个能返回结果的多线程任务,并且可以在主线程中获取该任务的执行结果,同时体现了多线程的并发执行特性,主线程和自定义线程可以同时(或交替)执行各自的任务。
class MyCallable implements Callable<Integer>{
private int sum=0;
@Override
public Integer call() throws Exception {
for(int i=1;i<=100;i++){
sum+=i;
}
System.out.println(Thread.currentThread().getName()+"累加完成!");
return sum;
}
}
public class CallableThreadDemo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable=new MyCallable();
FutureTask<Integer> futureTask=new FutureTask<>(myCallable);
Thread thread=new Thread(futureTask);
thread.start();
System.out.println(Thread.currentThread().getName()+"在处理自己的事情");
Integer value=futureTask.get();
System.out.println(value);
}
}
运行结果:
main在处理自己的事情
Thread-0累加完成!
5050
示例代码2️⃣(简易模拟火车卖票系统):
@Data
@NoArgsConstructor
@AllArgsConstructor
class CarTicket{
private int num=100;
}
class CallableThread implements Callable<Integer>{
private CarTicket carTicket;
public CallableThread(CarTicket carTicket){
this.carTicket=carTicket;
}
@Override
public Integer call() throws Exception {
while(true){
if(this.carTicket.getNum()>0){
carTicket.setNum(carTicket.getNum()-1);
System.out.println(Thread.currentThread().getName()+"卖出了一张票,目前还剩【"+carTicket.getNum()+"】");
}else{
System.out.println(Thread.currentThread().getName()+"网点的票已售完!");
break;
}
}
return carTicket.getNum();
}
}
public class CallableThreadDemo02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CarTicket carTicket=new CarTicket();
CallableThread callableThread=new CallableThread(carTicket);
FutureTask ft=new FutureTask<Integer>(callableThread);
Thread t1=new Thread(ft,"A站");
t1.start();
Thread t2=new Thread(new FutureTask<Integer>(callableThread),"B站");
t2.start();
Thread t3=new Thread(new FutureTask<Integer>(callableThread),"C站");
t3.start();
Thread t4=new Thread(new FutureTask<Integer>(callableThread),"D站");
t4.start();
System.out.println(ft.get());
}
}
运行结果:
C站卖出了一张票,目前还剩【1】
A站卖出了一张票,目前还剩【13】
B站卖出了一张票,目前还剩【17】
C站卖出了一张票,目前还剩【0】
D站卖出了一张票,目前还剩【2】
D站网点的票已售完!
A站网点的票已售完!
B站网点的票已售完!
C站网点的票已售完!
这段代码依然存在线程安全问题:多个线程同时操作共享的车票数量时,可能会出现超卖或重复售票的情况。后续会解决这个问题!
示例代码3️⃣(奇偶线程,累加):
public class CallableThreadDemo03 {
public static void main(String[] args) {
Thread t1=new Thread(new FutureTask<Integer>(()->{
int sum=0;
for(int i=1;i<=100;i+=2){
sum+=i;
System.out.println(Thread.currentThread().getName()+"正在做累加,当前累加的结果为:"+sum);
}
return sum;
}),"奇数线程");
t1.start();
Thread t2=new Thread(new FutureTask<Integer>(()->{
int sum=0;
for(int i=0;i<=100;i+=2){
sum+=i;
System.out.println(Thread.currentThread().getName()+"正在做累加,当前累加的结果为:"+sum);
}
return sum;
}),"偶数线程");
t2.start();
}
}
运行结果:
----------------------------------------------------------------
奇数线程正在做累加,当前累加的结果为:2209
奇数线程正在做累加,当前累加的结果为:2304
奇数线程正在做累加,当前累加的结果为:2401
奇数线程正在做累加,当前累加的结果为:2500
(4)利用线程池
预备知识:
线程池相关API:
●JDK 5.0起提供了线程池相关APl:ExecutorService和Executors
●ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
voidexecute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
<T>Future<T>submit(Callable<T>task):执行任务,有返回值,一般用来执行Callable
void shutdown():关闭连接池
●Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n);创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
下面利用工具类Executors来创建并返回不同类型的线程池:
语法:真正的线程池接口:ExecutorService pool=Executors(工具类).选择方法
1️⃣创建一个可重用的固定线程数的线程池:
public class ThreadPoolDemo01 {
public static void main(String[] args) {
ExecutorService pool= Executors.newFixedThreadPool(3); // 创建一个固定大小的线程池对象
for(int i=1;i<=50;i++){
final int k=i;
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+"正在处理任务-"+k);
});
}
pool.shutdown();
}
}
2️⃣创建一个可根据需要创建新线程(可扩展)的线程池:
class TicketThread2 implements Runnable{
private int num=100;
@Override
public void run() {
while(true){
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出了一张票,目前还剩["+(--num)+"]张票");
}else{
System.out.println(Thread.currentThread().getName()+"网点票已售完!");
break;
}
}
}
}
public class ThreadPoolDemo02 {
public static void main(String[l args){
ExecutorServicepool=Executors.newCachedThreadPool(); // 创建一个可扩展的线程池对象
TicketThread2 ticketThread2=newTicketThread2();
for(int i=1;i<=100;i++){
pool.submit(ticketThread2);
}
pool.shutdown();
}
}
3️⃣创建一个单一线程的线程池:
class TicketThread2 implements Runnable{
private int num=100;
@Override
public void run() {
while(true){
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出了一张票,目前还剩["+(--num)+"]张票");
}else{
System.out.println(Thread.currentThread().getName()+"网点票已售完!");
break;
}
}
}
}
public class ThreadPoolDemo02 {
public static void main(String[] args) {
ExecutorService pool=Executors.newSingleThreadExecutor();//创建一个具有单一线程的线程池对象
TicketThread2 ticketThread2=new TicketThread2();
for(int i=1;i<=100;i++){
pool.submit(ticketThread2);
}
pool.shutdown();
}
}
4️⃣创建一个可以定期执行或者可以延时执行的线程池:
public class ThreadPoolDemo03 {
public static void main(String[] args) {
final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
scheduledExecutorService.scheduleWithFixedDelay(()->{
long start=System.currentTimeMillis();
System.out.println("炸弹起爆!");
try {
Thread.sleep(4000);
long end=System.currentTimeMillis();
System.out.println("用时:"+(end-start));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},2,3, TimeUnit.SECONDS);
}
}
方法说明:
1.sleep 的作用
sleep(4000) 表示当前线程暂停(休眠)4秒(4000毫秒)。在这段时间内,线程不会做任何事情,只是等待。
2.delay 的作用
scheduleWithFixedDelay 方法的 delay 参数(如你代码中的 delay: 3, TimeUnit.SECONDS)表示每次任务执行结束后,等待多少时间再开始下一次任务。也就是说:上一次任务结束后,等待 delay 秒,再开始下一次任务。
3.关系总结
sleep 控制每次任务本身要执行多久(比如模拟任务耗时)。
delay 控制每次任务结束后,等待多久再开始下一次任务。



1万+

被折叠的 条评论
为什么被折叠?



