当前位置: 首页 > ai >正文

Java多线程学习笔记

Java多线程学习笔记

基本概念

进程与线程

  • 进程:程序的基本执行实体

  • 线程:操作系统中能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位

并行与并发

  • 并行:在同一时刻,有多个指令在多个CPU上同时进行

  • 并发:在同一时刻,有多个指令在单CPU上交替进行

多线程实现方式

1. 继承Thread类

// 1.1 创建一个继承Thread类的子类
// 1.2 重写run()方法
// 1.3 创建子类对象,并启动线程
class MyThread extends Thread {@Overridepublic void run() {// 线程执行的代码}
}
​
// 使用
MyThread t1 = new MyThread();
t1.setName("线程1");
t1.start();

特点:编程简单,可以直接使用Thread类中的方法,但扩展性较差,不能再继承其他的类

2. 实现Runnable接口

// 2.1 创建一个实现Runnable接口的实现类
// 2.2 实现run()方法
// 2.3 创建Runnable接口的实现类对象,创建Thread对象时作为一个参数来传递启动,并启动线程
class MyRun implements Runnable {@Overridepublic void run() {// 线程执行的代码}
}
​
// 使用
MyRun r1 = new MyRun();
Thread t1 = new Thread(r1);
t1.setName("线程1");
t1.start();

特点:扩展性强,实现该接口的同时还可以继承其他类,编程相对复杂,不能直接使用Thread类中的方法

3. 利用Callable接口和Future接口

// 3.1 创建一个类MyCallable实现Callable接口
// 3.2 重写call(),有返回值,表示多线程运行的结果
// 3.3 创建MyCallable对象(表示多线程要执行的任务)
// 3.4 创建Future对象(作用管理多线程运行的结果)
// 3.5 创建Thread类的对象,并启动(表示线程)
class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// 线程执行的代码return 100; // 返回结果}
}
​
// 使用
MyCallable c1 = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(c1);
Thread t1 = new Thread(ft);
t1.setName("线程1");
t1.start();
Integer result = ft.get(); // 获取结果

特点:可以获取多线程运行的结果,扩展性强,实现该接口的同时还可以继承其他类

线程的成员方法

方法描述
String getName()返回此线程的名称
void setName(String name)设置此线程的名称
static Thread currentThread()获取当前线程的对象
static void sleep(long time)让线程休眠指定的时间,单位为毫秒

细节

  1. 如果没有给线程设置名字,线程有默认的名字:Thread-0, Thread-1, ...

  2. 给线程设置名字可以使用set方法或构造方法

  3. 当JVM启动时,会自动启动多条线程,其中有一条是main线程

  4. sleep方法会让当前线程休眠指定时间,时间到了自动醒来

线程调度和优先级

调度方式

  • 抢占式调度:CPU会根据线程的优先级来调度线程,优先级高的线程会优先被CPU调度

  • 非抢占式调度:时间片轮转

线程优先级

public static final int MIN_PRIORITY = 1;    // 最小优先级
public static final int NORM_PRIORITY = 5;   // 默认优先级
public static final int MAX_PRIORITY = 10;   // 最高优先级
​
// 设置和获取优先级
thread.setPriority(int newPriority);
int priority = thread.getPriority();

注意:优先级不是绝对的,只是影响调度的概率

其他方法

  • final void setDaemon(boolean on):设置线程为守护线程

  • static void yield():出让线程/礼让线程

  • final void join():插入线程/插队线程

细节

  • 守护线程:当其他的非守护线程执行完毕之后,守护线程会陆续结束

  • yield():让出当前CPU使用权,让当前线程从运行状态进入到就绪状态

  • join():把指定线程插入到当前线程之前执行

线程的生命周期

状态描述
新建状态创建线程对象,但还没有启动线程
就绪状态调用start()方法,线程有执行资格,没有执行权
运行状态线程获取CPU执行权,执行run()方法
死亡状态线程执行完毕,不能再启动
阻塞状态线程遇到阻塞式方法,没有执行资格和执行权

线程安全与同步

同步代码块

synchronized(锁对象) {// 操作共享数据的代码
}

特点

  1. 锁默认打开,有一个线程进去了,锁自动关闭

  2. 里面的代码全部执行完毕,线程出来,锁自动打开

同步方法

// 非静态方法
public synchronized void method() {// 操作共享数据的代码
}
​
// 静态方法
public static synchronized void method() {// 操作共享数据的代码
}

特点

  1. 同步方法是锁住方法里面所有的代码

  2. 锁对象:非静态方法使用this,静态方法使用当前类的字节码文件对象(类名.class

字符串类线程安全

  • StringBuilder:线程不安全

  • StringBuffer:线程安全(每个成员方法都有synchronized修饰)

Lock锁

// 创建锁
Lock lock = new ReentrantLock();
​
// 使用锁
lock.lock();
try {// 操作共享数据的代码
} finally {lock.unlock();
}

特点:比synchronized更灵活,需要手动上锁和释放锁

死锁

死锁定义

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

产生死锁的原因

  1. 竞争资源

    • 可剥夺资源:CPU和主存

    • 不可剥夺资源:打印机、磁带机等

    • 临时资源:硬件中断、信号、消息等

  2. 进程间推进顺序非法

死锁产生的4个必要条件

  1. 互斥条件:进程要求对所分配的资源进行排它性控制

  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放

  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺

  4. 环路等待条件:在发生死锁时,必然存在一个进程-资源的环形链

解决死锁的基本方法

预防死锁
  1. 资源一次性分配:破坏请求条件

  2. 只要有一个资源得不到分配,也不给这个进程分配其他的资源:破坏请保持条件

  3. 可剥夺资源:破坏不可剥夺条件

  4. 资源有序分配法:破坏环路等待条件

具体技术
  1. 以确定的顺序获得锁

  2. 超时放弃:使用tryLock(long time, TimeUnit unit)方法

避免死锁
  • 银行家算法:系统在进行资源分配之前预先计算资源分配的安全性

检测死锁
  1. Jstack命令:生成java虚拟机当前时刻的线程快照

  2. JConsole工具:JDK自带的监控工具

解除死锁
  1. 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程

  2. 撤消进程:直接撤消死锁进程或撤消代价最小的进程

等待唤醒机制

方法

  • void wait():当前线程等待,直到被其他线程唤醒

  • void notify():随机唤醒单个线程

  • void notifyAll():唤醒所有线程

阻塞队列

接口层次

Iterable<E> → Collection<E> → Queue<E> → BlockingQueue<E>

实现类

  • ArrayBlockingQueue<E>:底层是数组,有界

  • LinkedBlockingQueue<E>:底层是链表,无界(但不是真正的无界,最大为int的最大值)

JVM定义的线程状态

JVM中定义了6种线程状态:

状态描述
NEW至今尚未启动的线程
RUNNABLE正在Java虚拟机中执行的线程
BLOCKED受阻塞并等待某个监视器锁的线程
WAITING无限期地等待另一个线程来执行某一特定操作的线程
TIMED_WAITING等待另一个线程来执行取决于等待时间的操作的线程
TERMINATED已经退出的线程

注意:JVM中没有运行态,因为线程抢夺到CPU的执行权时,JVM将线程交给操作系统管理。

线程数据共享方式

  1. 构造方法传递

  2. 静态变量

线程栈内存结构

  • 每个线程都有自己独立的栈内存

  • 当线程调用方法时,会形成栈帧并压入线程栈中

  • 栈帧包含:局部变量表、操作数栈、动态链接、方法出口等信息

  • 栈帧随着方法调用而创建,随着方法结束而销毁

线程池

核心原理

  1. 创建一个线程池,线程池中是空的

  2. 提交任务时,线程池会创建新的线程对象,任务执行完毕,线程归还给线程池

  3. 再次提交任务时,不需要创建新的线程,直接复用已有的线程

  4. 如果提交任务时,线程池中没有空闲线程,也无法创建新线程,任务就会排队等待

三个临界点

  1. 当核心线程满时,再提交任务就会排队

  2. 当核心线程满,阻塞队列满时,会创建临时线程

  3. 当核心线程满,阻塞队列满,临时线程满时,会触发任务拒绝策略

Executor框架

Executor框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等。

框架结构

  1. 任务Runnable接口或Callable接口

  2. 任务的执行Executor接口和ExecutorService接口

  3. 异步计算的结果Future接口和FutureTask

使用步骤

  1. 创建实现RunnableCallable接口的任务对象

  2. 把任务对象交给ExecutorService执行

  3. 如果执行submit()方法,将返回一个Future对象

  4. 主线程可以执行Future.get()等待任务完成,或Future.cancel()取消任务

Executors工具类

  • Executors.newCachedThreadPool():创建一个没有上限的线程池

  • Executors.newFixedThreadPool(int nThreads):创建一个固定大小的线程池

ThreadPoolExecutor类

线程池实现类ThreadPoolExecutor是Executor框架最核心的类。

构造方法

public ThreadPoolExecutor(int corePoolSize,      // 线程池的核心线程数量int maximumPoolSize,    // 线程池的最大线程数long keepAliveTime,     // 空闲线程存活时间TimeUnit unit,          // 时间单位BlockingQueue<Runnable> workQueue, // 任务队列ThreadFactory threadFactory,      // 线程工厂RejectedExecutionHandler handler) // 拒绝策略

参数说明

参数描述
corePoolSize线程池的核心线程数量
maximumPoolSize线程池的最大线程数
keepAliveTime当线程数大于核心线程数时,多余的空闲线程存活的最长时间
unit时间单位
workQueue任务队列,用来储存等待执行的任务
threadFactory线程工厂,用来创建线程
handler拒绝策略,当提交的任务过多而不能及时处理时,定制策略来处理任务

线程池大小设置

需要根据任务类型和系统资源合理设置线程池大小:

  • CPU密集型任务:线程数 ≈ CPU核心数

  • I/O密集型任务:线程数 ≈ CPU核心数 * (1 + 平均等待时间/平均工作时间)

代码示例

// 继承Thread类
class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() + ": " + i);}}
}
​
// 实现Runnable接口
class MyRun implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ": " + i);}}
}
​
// 实现Callable接口
class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i < 100; i++) {sum += i;}return sum;}
}
​
// 售票示例
class Tickets extends Thread {private static int ticket = 100;
​@Overridepublic void run() {while (true) {if (ticket <= 0) {break;} else {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + "卖出第" + ticket + "张票");ticket--;}}}
}
​
// 实现Runnable接口的售票示例
class TicketsRunnable implements Runnable {private int ticket = 100;
​@Overridepublic void run() {while (true) {synchronized (this) {if (ticket <= 0) {break;} else {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖出第" + ticket + "张票");ticket--;}}}}
}
​
// 线程池任务
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "执行任务");}
}

http://www.xdnf.cn/news/20282.html

相关文章:

  • Nginx 实战系列(二)—— Nginx 配置文件与虚拟主机搭建
  • QML Charts组件之LineSeries、SplineSeries与ScatterSeries
  • 正态分布 - 正态分布的经验法则(68-95-99.7 法则)
  • Modbus通信的大端和小端字节序
  • OpsManage 项目启动脚本与 Docker 配置深度分析
  • Day05 单调栈 | 84. 柱状图中最大的矩形、42. 接雨水
  • LeetCode算法日记 - Day 34: 二进制求和、字符串相乘
  • 【目录-多选】鸿蒙HarmonyOS开发者基础
  • 分布式go项目-搭建监控和追踪方案
  • 国内外支持个人开发者的应用市场
  • OpenCV - 图像的IO操作
  • 【开题答辩全过程】以 住院管理系统为例,包含答辩的问题和答案
  • 从零开始的python学习——文件
  • C++ 面向对象编程:多态相关面试简答题
  • 444444
  • LeetCode - 1089. 复写零
  • MQTT 与 Java 框架集成:Spring Boot 实战(三)
  • RAG提示词分解
  • CentOS系统管理:useradd命令的全面解析
  • Vllm-0.10.1:通过vllm bench serve测试TTFT、TPOT、ITL、E2EL四个指标
  • 多线程任务执行窗体框架jjychengTaskWinForm
  • 浅析Linux内核scatter-gather list实现
  • SQL 实战指南:电商订单数据分析(订单 / 用户 / 商品表关联 + 统计需求)
  • WordPress过滤文章插入链接rel属性noopener noreferrer值
  • 开源与定制化对比:哪种在线教育系统源码更适合教育培训APP开发?
  • 企业微信智能表格高效使用指南
  • Kafka Exactly-Once 语义深度解析与性能优化实践指南
  • 串口发送数据
  • 如何离线安装 VirtualMachinePlatform
  • 基于STM32单片机的家庭医护血氧体温血压吃药监测APP系统