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

【Java多线程-----复习】

1. 什么是多线程

多线程是指在一个程序中同时运行多个执行线程的编程技术。

基本概念

  • 线程是程序执行的最小单位,比进程更轻量级
  • 同一个进程内的多个线程共享内存空间、文件句柄等系统资源
  • 每个线程有自己独立的执行栈和程序计数器

主要优势:

  • 提高效率:充分利用多核CPU,实现真正的并行处理
  • 响应性好:用户界面线程可以保持响应,而其他线程处理后台任务
  • 资源共享:线程间通信比进程间通信更高效

2. 线程基础

2.1 创建一个线程

2.1.1 创建一个简单线程

创建一个新线程可以通过实例化一个Thread实例,然后调用start()方法。
但是这个线程启动后实际上什么也不做就立刻结束了。

public class Main {public static void main(String[] args) {Thread t = new Thread();t.start(); // 启动新线程}
}

2.1.2 希望新线程能够执行制定代码

需要重写run()方法,通常可以继承Thread类或实现Runnable接口

// 方法1:从Thread派生一个自定义类,然后覆写run()方法:
public class Main {public static void main(String[] args) {Thread t = new MyThread();t.start(); // 启动新线程}
}class MyThread extends Thread {@Overridepublic void run() {System.out.println("start new thread!");}
}
// 方法2:创建Thread实例时,传入一个Runnable实例:
public class Main {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start(); // 启动新线程}
}class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("start new thread!");}
}
// 方法3:用Java 8引入的lambda语法进一步简写为:
// 多线程
public class Main {public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("start new thread!");});t.start(); // 启动新线程}
}

2.2 Java线程的状态

Java线程有6种状态,定义在Thread.State枚举中:

  1. New (新建状态)
    • 线程已创建但还未调用start()方法
  2. RUNNABLE (可运行状态)
    • 调用start()方法后进入此状态
    • 包含两个子状态:
      • Ready: 准备运行,等待CPU调度
      • Running: 正在CPU上执行
  3. BLOCKED (阻塞状态)
    • 线程被阻塞等待监视器锁
    • 通常发生在等待进入synchronized代码块/方法时
  4. WAITING (等待状态)
    • 线程无限期等待另一个线程的特定操作
    • 常见触发方式:
      • Object.wait()
      • Thread.join()
      • LockSupport.park()
  5. TIMED_WAITING (超时等待状态)
    • 线程等待指定时间后自动返回
    • 常见触发方式:
      • Thread.sleep(long)
      • Object.wait(long)
      • Thread.join(long)
  6. TERMINATED (终止状态)
    • 线程执行完毕或异常退出后的状态
    • 线程一旦进入此状态就无法再次启动

2.3 如何中断线程

如果线程需要执行一个长时间任务,就可能需要能中断线程。中断线程就是其他线程给该线程发一个信号,该线程收到信号后结束执行run()方法,使得自身线程能立刻结束运行。

2.3.1 中断线程

// 中断线程
public class Main {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();t.start();Thread.sleep(1); // 暂停1毫秒t.interrupt(); // 中断t线程t.join(); // 等待t线程结束System.out.println("end");}
}class MyThread extends Thread {public void run() {int n = 0;while (! isInterrupted()) {n ++;System.out.println(n + " hello!");}}
}

2.3.2 基于自己设置的标志位中断线程

// 中断线程
public class Main {public static void main(String[] args)  throws InterruptedException {HelloThread t = new HelloThread();t.start();Thread.sleep(1);t.running = false; // 标志位置为false}
}class HelloThread extends Thread {public volatile boolean running = true;public void run() {int n = 0;while (running) {n ++;System.out.println(n + " hello!");}System.out.println("end!");}
}

2.3.2 中断父线程

main线程通过调用t.interrupt()从而通知t线程中断,而此时t线程正位于hello.join()的等待中,此方法会立刻结束等待并抛出InterruptedException。

由于我们在t线程中捕获了InterruptedException,因此,就可以准备结束该线程。在t线程结束前,对hello线程也进行了interrupt()调用通知其中断。如果去掉这一行代码,可以发现hello线程仍然会继续运行,且JVM不会退出。

// 中断线程
public class Main {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();t.start();Thread.sleep(1000);t.interrupt(); // 中断t线程t.join(); // 等待t线程结束System.out.println("end");}
}class MyThread extends Thread {public void run() {Thread hello = new HelloThread();hello.start(); // 启动hello线程try {hello.join(); // 等待hello线程结束} catch (InterruptedException e) {System.out.println("interrupted!");}// 这段代码很重要,不然会导致hello线程无法结束,主线程也无法结束hello.interrupt();}
}class HelloThread extends Thread {public void run() {int n = 0;while (!isInterrupted()) {n++;System.out.println(n + " hello!");try {Thread.sleep(100);} catch (InterruptedException e) {break;}}}
}

3. 线程同步

3.1 synchronized

用synchronized修饰方法可以把整个方法变为同步代码块,synchronized方法加锁对象是this;通过合理的设计和数据封装可以让一个类变为“线程安全”;

public class Counter {private int count = 0;public void add(int n) {synchronized(this) {count += n;}}public void dec(int n) {synchronized(this) {count -= n;}}public int get() {return count;}
}

3.2 使用wait和notify

import java.util.*;class TaskQueue {Queue<String> queue = new LinkedList<>();public synchronized void addTask(String s) {this.queue.add(s);this.notifyAll();}public synchronized String getTask() throws InterruptedException {while (queue.isEmpty()) {this.wait();}return queue.remove();}
}

3.3 ReentrantLock

public class Counter {private final Lock lock = new ReentrantLock();private int count;public void add(int n) {lock.lock();try {count += n;} finally {lock.unlock();}}
}

3.4 Condition

使用ReentrantLock比直接使用synchronized更安全,可以替代synchronized进行线程同步。但是,synchronized可以配合wait和notify实现线程在条件不满足时等待,条件满足时唤醒,用ReentrantLock我们怎么编写wait和notify的功能呢?

答案是使用Condition对象来实现wait和notify的功能

class TaskQueue {private final Lock lock = new ReentrantLock();private final Condition condition = lock.newCondition();private Queue<String> queue = new LinkedList<>();public void addTask(String s) {lock.lock();try {queue.add(s);condition.signalAll();} finally {lock.unlock();}}public String getTask() {lock.lock();try {while (queue.isEmpty()) {condition.await();}return queue.remove();} finally {lock.unlock();}}
}

3.5 Semaphore信号量

本质上锁的目的是保护一种受限资源,保证同一时刻只有一个线程能访问(ReentrantLock),或者只有一个线程能写入(ReadWriteLock)。
还有一种受限资源,它需要保证同一时刻最多有N个线程能访问,比如同一时刻最多创建100个数据库连接,最多允许10个用户下载等。

public class AccessLimitControl {// 任意时刻仅允许最多3个线程获取许可:final Semaphore semaphore = new Semaphore(3);public String access() throws Exception {// 如果超过了许可数量,其他线程将在此等待:semaphore.acquire();try {// TODO:return UUID.randomUUID().toString();} finally {semaphore.release();}}
}

3.6 Concurrent集合

interfacenon-thread-safethread-safe
ListArrayListCopyOnWriteArrayList
SetHashSet / TreeSetCopyOnWriteArraySet
MapHashMapConcurrentHashMap
QueueArrayDeque / LinkedListArrayBlockingQueue / LinkedBlockingQueue
DequeArrayDeque / LinkedListLinkedBlockingDeque

Java的java.util.concurrent包除了提供底层锁、并发集合外,还提供了一组原子操作的封装类,它们位于java.util.concurrent.atomic包。

以AtomicInteger为例,它提供的主要操作有:

  • 增加值并返回新值:int addAndGet(int delta)
  • 加1后返回新值:int incrementAndGet()
  • 获取当前值:int get()
  • 用CAS方式设置:int compareAndSet(int expect, int update)
http://www.dtcms.com/a/289574.html

相关文章:

  • InfluxDB 核心概念与发展历程全景解读(二)
  • AtCoder Beginner Contest 415
  • 数控调压BUCK电路 —— 基于TPS56637(TI)
  • 乐观锁实现原理笔记
  • Android activity与service通信的三种方法
  • 30天打牢数模基础-K均值聚类
  • (DINO)Emerging Properties in Self-Supervised Vision Transformers论文精读(逐段解析)
  • (苍穹外卖)暑假学习理解P2
  • 从零搭建智能搜索代理:LangGraph + 实时搜索 + PDF导出完整项目实战
  • [C/C++安全编程]_[中级]_[如何安全使用循环语句]
  • k8s:离线部署存在的相关问题
  • 近期工作感想:职业规划篇
  • 【单片机外部中断实验修改动态数码管0-99】2022-5-22
  • Linux文件系统三要素:块划分、分区管理与inode结构解析
  • 本地部署开源离线内容浏览器 Kiwix 并实现外部访问( Windows 版本)
  • 【Java新特性】Java 21 新特性全解析
  • CSS面试题及详细答案140道之(121-140)
  • 快速理解LLM的temperature和top_p参数
  • 设备健康管理实施案例:从技术架构到落地效果的全栈解析
  • MCP实战案例|Trae2.0 一键创建旅行助手并一键部署EdgeOne
  • ARFoundation系列讲解 - 101 VisionPro 真机调试
  • Vue中组件的生命周期
  • 建筑墙壁损伤缺陷分割数据集labelme格式7820张20类别
  • Django ORM系统
  • [学习] Hilbert变换:从数学原理到物理意义的深度解析与仿真实验(完整实验代码)
  • 平安车管家|中国平安车管家入职测评16PF瑞文IQ测评答题攻略及真题题库
  • 嵌入式系统内核镜像相关(十七)
  • AI 的广泛应用是否会削弱人的思考能力和创造力?
  • GaussDB select into和insert into的用法
  • 字符串处理