Java多线程编程初阶指南

news2025/4/23 9:05:33

目录

一.线程基础概念

线程是什么?

线程与进程对比

为啥要有线程 

二.线程实现方式 

继承Thread类 

 实现Runnable接口

常规实现方式

 匿名内部类写法

 Lambda表达式写法(Java8+) 

对比总结

 三.Thread 类及常见方法

核心功能

核心构造方法

核心属性获取方法

线程控制核心方法 

启动线程:start()

中断控制:interrupt()

线程等待:join()

线程休眠:sleep()

 四.线程的状态

NEW(新建状态)

RUNNABLE(可运行状态)

BLOCKED(阻塞状态)

 WAITING(无限等待)

TIMED_WAITING(限期等待)

五.线程安全(重点) 

什么是线程安全? 

原子性(Atomicity) 

可见性(Visibility)

有序性(Ordering)

解决之前的线程不安全问题 

synchronized 关键字 

volatile 关键字

原子性问题解决方案 

可见性问题解决方案 

有序性问题解决方案 

六.wait()与notify() 

核心作用解析

1. 线程协调的基石

2. 方法定位

标准使用模板

1. 基础协作模式

 2. 生产者-消费者实现

关键使用原则

1. 必须遵守的黄金法则

2. 选择通知策略

3. 超时控制(避免永久阻塞)

 七.多线程案例

单例模式

阻塞式队列

定时器

线程池

总结-保证线程安全的思路


一.线程基础概念

线程是什么?

线程是操作系统调度的最小执行单元,属于同一进程的多个线程共享进程资源(内存、文件句柄等),每个线程独立执行代码指令序列。

  • ​执行流​​:线程是程序中的独立执行路径,多个线程可并发执行
  • ​轻量级进程​​:相比进程,线程更轻量(创建/销毁更快,共享内存空间)
  • ​主从关系​​:主线程创建子线程,共同完成任务

线程与进程对比

特性进程线程
资源隔离独立内存空间共享进程内存
创建开销高(系统级资源分配)低(仅需栈和程序计数器)
通信效率低(需IPC机制)高(直接内存共享)
上下文切换耗时(内存映射切换)快速(仅寄存器切换)

为啥要有线程 

首先, "并发编程" 成为 "刚需". 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU 资源. 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.

其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量. 创建线程比创建进程更快. 销毁线程比销毁进程更快. 调度线程比调度进程更快. 

二.线程实现方式 

继承Thread类 

// 传统继承方式
class CustomThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程ID: " + getId());
    }
}

// 创建启动
new CustomThread().start();

特点​​:

  • 直接继承Thread类
  • 通过重写run()方法定义任务
  • 适用于简单场景,但破坏了Java单继承特性

 实现Runnable接口

常规实现方式
class Task implements Runnable {
    @Override
    public void run() {
        System.out.println("传统Runnable实现");
    }
}

// 创建启动
new Thread(new Task()).start();
 匿名内部类写法
// 匿名内部类实现
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("匿名内部类线程");
        // 可访问外部final变量
    }
}).start();

优势​​:

  • 适用于一次性任务
  • 支持访问外部final变量
  • 示例:事件监听等临时性任务
 Lambda表达式写法(Java8+) 
// Lambda简化实现
new Thread(() -> {
    System.out.println("Lambda表达式线程");
    // 支持多行代码
}).start();

// 单行语句简写
new Thread(() -> System.out.println("极简Lambda线程")).start();

特点​​:

  • 要求接口是函数式接口(单一抽象方法)
  • 自动类型推导
  • 代码简洁度提升
  • 推荐用于简单任务场景
对比总结
实现方式代码行数可复用性灵活性推荐指数
继承Thread5-10★☆☆☆☆
常规Runnable6-8★★★☆☆
匿名内部类3-5★★★★☆
Lambda表达式1-3极高★★★★★

 三.Thread 类及常见方法

核心功能

  • 线程创建与启动
  • 线程状态管理
  • 线程优先级控制
  • 线程中断处理
  • 线程同步协作

核心构造方法

构造方法适用场景
Thread()创建空线程对象
Thread(Runnable target)标准Runnable实现(推荐)
Thread(String name)创建命名线程
Thread(Runnable target, String name)命名+Runnable实现
Thread(ThreadGroup group, ...)线程组管理(高级用法)

核心属性获取方法

方法返回值类型说明
getId()long获取线程唯一ID
getName()String获取线程名称
getState()State获取线程状态
getPriority()int获取优先级(1-10)
isAlive()boolean判断线程是否存活
isInterrupted()boolean中断状态检查(不清除标志)

线程控制核心方法 

启动线程:start()

​作用​​:启动线程执行,使线程进入可运行状态(RUNNABLE)

Thread t = new Thread(task);
t.start();  // 正确启动方式
t.run();    // 错误!在调用线程同步执行

执行流程​​:

  1. JVM创建新线程(从NEW状态转换)
  2. 调用run()方法
  3. 线程进入RUNNABLE状态
  4. 由线程调度器分配CPU时间片

​关键特性​​:

  • 每个线程只能调用一次start()
  • 实际执行顺序由OS调度器决定
  • 直接调用run()方法不会创建新线程

中断控制:interrupt()

​作用​​:设置线程中断标志/唤醒阻塞中的线程

Thread worker = new Thread(() -> {
    while (!Thread.interrupted()) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("接收中断信号");
            break;
        }
    }
});
worker.start();
worker.interrupt();  // 发送中断信号

​阻塞状态响应​​:

  • sleep()/wait()/join()会立即抛出InterruptedException

  • 抛出异常后自动清除中断标志

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // 此处中断标志已被清除
    Thread.currentThread().interrupt(); // 恢复中断状态
}

中断标志检查​​:

// 方法1:检查并清除标志(静态方法)
if (Thread.interrupted()) {
    // 处理中断逻辑
}

// 方法2:检查标志不改变状态(实例方法)
if (thread.isInterrupted()) {
    // 处理中断逻辑
}

线程等待:join()

​作用​​:使当前线程等待目标线程终止

Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);

t1.start();
t1.join(500);  // 最多等待500ms
System.out.println("t1状态: " + t1.getState());

t2.start();
t2.join();     // 无限等待直至完成

方法重载​​:

方法签名作用描述
void join()无限等待直至线程终止
void join(long millis)最多等待指定毫秒数
void join(long millis, int nanos)高精度等待(实际精度依赖系统实现)

注意事项​​:

  • 调用join()的线程会进入WAITING/TIMED_WAITING状态
  • 可被interrupt()中断等待
  • 不要在主线程中join自身(导致死锁)

线程休眠:sleep()

​作用​​:使当前线程暂停执行指定时间

try {
    Thread.sleep(2000);         // 休眠2秒
    Thread.sleep(1500, 500000); // 1.5秒+500000纳秒
} catch (InterruptedException e) {
    // 中断处理逻辑
}

核心特性​​:

特性说明
锁保持休眠期间保持持有的所有锁
时间精度实际休眠时间>=参数值(受系统计时器和调度器影响)
中断响应休眠期间收到中断会抛出InterruptedException
状态转换进入TIMED_WAITING状态

​​典型应用场景​​:

  • 模拟耗时操作

  • 控制任务执行频率

  • 防止CPU空转消耗资源

 注意要点​​:

// 错误用法:用sleep实现同步控制
while (!condition) {
    Thread.sleep(100); // 应使用wait/notify机制
}

// 正确用法:定时任务间隔
void run() {
    while (running) {
        processTask();
        Thread.sleep(1000); // 每秒执行一次
    }
}

 四.线程的状态

状态描述
NEW创建未启动
RUNNABLE可运行(包括就绪和运行中)
BLOCKED等待监视器锁
WAITING无限期等待其他线程通知
TIMED_WAITING带超时的等待
TERMINATED执行完成

NEW(新建状态)

​特征​​:

  • 线程对象已创建
  • 未调用start()方法
  • 未分配系统资源

​触发条件​​:

Thread t = new Thread(() -> {});
System.out.println(t.getState()); // 输出NEW

RUNNABLE(可运行状态)

​子状态​​:

  • Ready:等待CPU时间片
  • Running:正在执行

​触发条件​​:

t.start();  // 从NEW进入RUNNABLE
synchronized(lock) { 
    // 锁释放后从BLOCKED回到RUNNABLE
}

BLOCKED(阻塞状态)

​产生场景​​:

  • 等待进入synchronized代码块
  • 等待进入synchronized方法

​触发条件​​: 

Object lock = new Object();

// 线程1
new Thread(() -> {
    synchronized(lock) {
        while(true); // 长期持有锁
    }
}).start();

// 线程2
Thread t2 = new Thread(() -> {
    synchronized(lock) { // 此处阻塞
        System.out.println("获得锁");
    }
});
t2.start();
System.out.println(t2.getState()); // 输出BLOCKED

 WAITING(无限等待)

​触发条件​​:

object.wait();   // 未设置超时
thread.join();   // 未设置超时
LockSupport.park();

状态特征​​:

  • 需要其他线程主动唤醒
  • 不占用CPU资源
  • 常见于线程协调场景

TIMED_WAITING(限期等待)

典型方法​​: 

Thread.sleep(1000);
object.wait(500);
thread.join(3000);
LockSupport.parkNanos(1000000);

代码示例​​:

Thread t = new Thread(() -> {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
t.start();
System.out.println(t.getState()); // 输出TIMED_WAITING

​终止条件​​:

  • run()方法正常执行结束
  • 未捕获异常导致线程终止

​触发条件​​:

Thread t = new Thread(() -> {});
t.start();
t.join();
System.out.println(t.getState()); // 输出TERMINATED

五.线程安全(重点) 

什么是线程安全? 

​核心定义​​:当多个线程并发访问共享资源时,程序仍能保持正确行为的状态

​黄金准则​​:当且仅当满足以下条件时,程序才是线程安全的:

  1. 原子性(Atomicity)保证
  2. 可见性(Visibility)保证
  3. 有序性(Ordering)保证

原子性(Atomicity) 

原子性要求对共享资源的操作是不可分割的完整单元,其他线程只能看到操作前或操作后的状态,不能观察到中间状态。 

// 非原子操作示例
public class UnsafeCounter {
    private int count = 0;
    
    public void increment() {
        count++; // 实际包含三个步骤:读值、修改、写回
    }
}
//mov eax, [count]  ; 读取当前值到寄存器
//inc eax           ; 寄存器值加1
//mov [count], eax  ; 将新值写回内存

当两个线程交错执行时:

线程A:读取count=5 → 暂停
线程B:完成完整操作count=6
线程A:继续执行,最终count=6(正确值应为7)

可见性(Visibility)

 可见性保证一个线程修改共享变量后,其他线程能立即感知到最新值。

可见性问题根源(Java内存模型)​

  • ​工作内存机制​​:每个线程有独立的工作内存(CPU缓存),优先读取本地缓存而非主内存

  • ​缺乏同步机制​​:普通变量修改后不强制刷新到主内存,其他线程可能读取到过期值

  • ​JIT编译器优化​​:热点代码可能被优化为直接使用寄存器中的值,跳过内存读取

public class VisibilityDemo {
    // 不加volatile会导致死循环
    private static boolean flag = true; // 无volatile修饰

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (flag) { 
                // 空循环(无同步操作)
            }
            System.out.println("线程退出");
        }).start();

        Thread.sleep(1000);
        flag = false; // 主线程修改标志位
    }
}

导致死循环的详细原因​

​1 工作内存缓存​

​子线程启动时​​:

  • 从主内存读取flag=true到工作内存

  • 后续循环直接使用工作内存中的副本值

​主线程修改flag后​​:

  • 修改的是主内存中的值(但不确定何时刷新)

  • 子线程的工作内存副本未失效,仍认为flag=true

2 JIT编译器优化​

  • 当循环检测到flag未被修改时,可能进行激进优化:

; 伪汇编代码(优化后)
LOOP:
  cmp [flag], true  // 被优化删除
  jmp LOOP          // 直接无限循环
  • ​优化依据​​:循环体内无任何同步操作,假设flag不会改变

3 内存屏障缺失​

普通变量的读写​​不插入内存屏障​​:

  • 主线程修改flag后不强制刷新到主内存

  • 子线程不强制从主内存重新加载值

有序性(Ordering)

 有序性保证程序执行顺序符合代码的先后关系,防止编译器和处理器优化导致的指令重排序。

// 危险的双重检查锁
public class Singleton {
    private static Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {                 // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {        // 第二次检查
                    instance = new Singleton(); // 问题根源
                }
            }
        }
        return instance;
    }
}

对象初始化过程可能被重排序为:

  1. 分配内存空间
  2. 将引用指向内存空间(此时instance != null)
  3. 初始化对象

导致其他线程可能获得未初始化的实例。

解决之前的线程不安全问题 

synchronized 关键字 

核心作用

  • ​原子性​​:确保代码块内的操作不可分割

  • ​可见性​​:修改后的变量值对其他线程立即可见

  • ​有序性​​:防止代码块内的指令重排序

 使用场景

  • 需要保证复合操作的原子性(如:计数器累加)

  • 多线程共享资源的互斥访问(如:数据库连接池)

  • 需要实现线程间协作(配合wait()/notify()

使用方法 

必须显式指定锁对象​​,推荐使用私有final对象,避免使用this或公共对象

实例锁(对象级别) 

public class OrderService {
    // 1. 创建私有final锁对象(强制不可修改)
    private final Object orderLock = new Object();
    
    private int stock = 100;

    public void placeOrder() {
        // 2. 使用指定锁对象同步
        synchronized(orderLock) { 
            if(stock > 0) {
                stock--;
                System.out.println("下单成功,剩余库存:" + stock);
            }
        }
    }
}

类锁(全局级别) 

public class GlobalCounter {
    // 1. 创建类级别锁对象(static final)
    private static final Object CLASS_LOCK = new Object();
    
    private static int count = 0;

    public static void increment() {
        // 2. 使用类锁对象同步
        synchronized(CLASS_LOCK) {
            count++;
        }
    }
}

volatile 关键字

核心作用

  • ​可见性​​:变量修改后立即对所有线程可见

  • ​有序性​​:禁止指令重排序优化

使用场景

  • 状态标志位(如:线程终止标志)

  • 单次原子操作(如:long/double类型变量)

  • 双重检查锁定模式(DCL单例)

 使用方法 

 状态标志控制​

public class WorkerThread implements Runnable {
    private volatile boolean running = true;

    public void run() {
        while(running) {
            // 执行任务...
        }
    }

    public void stop() {
        running = false;  // 其他线程修改后立即生效
    }
}

 单例模式实现​

public class Singleton {
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if(instance == null) {
            synchronized(Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();  // volatile防止指令重排序
                }
            }
        }
        return instance;
    }
}

 独立观察变量​

public class SensorMonitor {
    private volatile double temperature;
    
    // 多个线程读取温度值
    public double getCurrentTemp() {
        return temperature;  // 总是获取最新值
    }
    
    // 专用线程更新温度值
    public void updateTemp(double newValue) {
        temperature = newValue;
    }
}

原子性问题解决方案 

synchronized同步锁 

public class SafeCounter {
    private int count = 0;
    
    public synchronized void increment() {
        count++; // 保证原子操作的黄金方案
    }
}

实现原理​​:

  • Monitor锁机制确保同一时刻只有一个线程能访问临界区
  • 内存屏障强制工作内存与主内存同步
  • 可重入设计避免死锁

​适用场景​​:

  • 复杂的复合操作
  • 需要保证操作完整性的关键业务

可见性问题解决方案 

volatile关键字 

public class VisibilitySolution {
    private volatile boolean flag = true;
    
    public void start() {
        new Thread(() -> {
            while(flag) { /* 即时可见 */ }
        }).start();
    }
}

内存屏障机制​​:

  1. 写操作前插入StoreStore屏障
  2. 写操作后插入StoreLoad屏障
  3. 读操作前插入LoadLoad屏障

​适用限制​​:

  • 仅适用于单个变量的状态标记
  • 不保证复合操作的原子性

有序性问题解决方案 

双重检查锁优化 

public class Singleton {
    private static volatile Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(); 
                }
            }
        }
        return instance;
    }
}

​volatile的魔法作用​​:

  1. 禁止new操作的指令重排序
  2. 保证对象初始化完成后才赋值引用
  3. 内存屏障阻止其他线程访问未初始化对象

六.wait()与notify() 

核心作用解析

1. 线程协调的基石

wait()notify()是Java线程间通信的基础方法,用于解决以下典型场景:

  • 生产者消费者模式(缓冲队列控制)
  • 任务协调(主从线程协作)
  • 资源调度(线程按条件执行)
2. 方法定位
方法作用描述
wait()释放锁并暂停线程,直到其他线程通知
notify()随机唤醒一个等待线程
notifyAll()唤醒所有等待线程

标准使用模板

1. 基础协作模式
public class TaskCoordinator {
    private final Object lock = new Object();
    private boolean isReady = false;

    // 等待侧代码
    public void waitForCondition() throws InterruptedException {
        synchronized(lock) {
            while(!isReady) {        // 必须使用循环检查
                lock.wait();         // 释放锁并等待
            }
            // 条件满足后执行任务
            System.out.println("执行核心业务...");
        }
    }

    // 通知侧代码
    public void notifyCondition() {
        synchronized(lock) {
            isReady = true;         // 修改条件状态
            lock.notifyAll();       // 推荐使用notifyAll
        }
    }
}
 2. 生产者-消费者实现
public class MessageBuffer {
    private final LinkedList<String> queue = new LinkedList<>();
    private final int maxSize;
    private final Object lock = new Object();

    public MessageBuffer(int maxSize) {
        this.maxSize = maxSize;
    }

    // 生产者方法
    public void produce(String message) throws InterruptedException {
        synchronized(lock) {
            while(queue.size() == maxSize) {  // 缓冲区满时等待
                lock.wait();
            }
            queue.add(message);
            lock.notifyAll();  // 唤醒所有消费者
        }
    }

    // 消费者方法
    public String consume() throws InterruptedException {
        synchronized(lock) {
            while(queue.isEmpty()) {          // 缓冲区空时等待
                lock.wait();
            }
            String message = queue.removeFirst();
            lock.notifyAll();  // 唤醒所有生产者
            return message;
        }
    }
}

关键使用原则

1. 必须遵守的黄金法则
  • ​同步块内使用​​:必须在synchronized代码块中调用
  • ​循环检查条件​​:防止虚假唤醒(spurious wakeup)
  • ​私有锁对象​​:推荐使用专用Object实例作为锁
// 正确示例:私有锁对象
private final Object lock = new Object();

// 错误示例:使用公共对象锁
public Object publicLock = new Object();
2. 选择通知策略
场景策略选择说明
单一等待条件notify()随机唤醒一个线程
多个等待条件notifyAll()避免线程饿死
复杂条件判断notifyAll()确保所有等待线程重新检查条件
3. 超时控制(避免永久阻塞)
public void timedWait() throws InterruptedException {
    synchronized(lock) {
        long timeout = 5000; // 5秒超时
        long remaining = timeout;
        long startTime = System.currentTimeMillis();
        
        while(!condition && remaining > 0) {
            lock.wait(remaining);
            remaining = timeout - (System.currentTimeMillis() - startTime);
        }
        
        if(condition) {
            // 执行后续操作
        }
    }
}

 七.多线程案例

单例模式

单例模式是校招中最常考的设计模式之一.

啥是设计模式? 设计模式好比象棋中的 "棋谱". 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有 一些固定的套路. 按照套路来走局势就不会吃亏. 软件开发中也有很多常见的 "问题场景". 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照 这个套路来实现代码, 也不会吃亏. 单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例. 这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个.

单例模式具体的实现方式, 分成 "饿汉" 和 "懒汉" 两种 

饿汉模式

类加载的同时, 创建实例. 

class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
   }
}

懒汉模式-单线程版

类加载的时候不创建实例. 第一次使用的时候才创建实例

class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
       }
        return instance;
   }
}

 懒汉模式-多线程版

上面的懒汉模式的实现是线程不安全的.

线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致 创建出多个实例. 一旦实例已经创建好了, 后面再多线程环境调用 getInstance 就不再有线程安全问题了(不再修改 instance 了)

加上 synchronized 可以改善这里的线程安全问题

class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
       }
        return instance;
   }
}

懒汉模式-多线程版(改进)

以下代码在加锁的基础上, 做出了进一步改动:

  • 使用双重 if 判定, 降低锁竞争的频率.
  • 给 instance 加上了 volatile.
class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
           if (instance == null) {
               instance = new Singleton();
               }
           }
       }
        return instance;
   }
}

阻塞式队列

阻塞队列是什么

阻塞队列是一种特殊的队列. 也遵守 "先进先出" 的原则.

阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
  • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

阻塞队列的一个典型应用场景就是 "生产者消费者模型". 这是一种非常典型的开发模型. 

基础版阻塞队列 

/**
 * 线程安全的阻塞队列实现
 * @param <T> 队列元素类型
 */
public class SimpleBlockingQueue<T> {
    // 存储队列元素的数组(循环数组)
    private final Object[] items;
    // 当前队列中的元素数量
    private int count;
    // 下一个插入位置的索引
    private int putIndex;
    // 下一个取出位置的索引
    private int takeIndex;
    // 同步锁对象(推荐使用专用锁对象)
    private final Object lock = new Object();

    /**
     * 初始化指定容量的队列
     * @param capacity 队列容量(必须>0)
     */
    public SimpleBlockingQueue(int capacity) {
        this.items = new Object[capacity];
    }

    /**
     * 将元素放入队列(队列满时阻塞等待)
     * @param item 要添加的元素(不能为null)
     * @throws InterruptedException 等待时被中断抛出
     */
    public void put(T item) throws InterruptedException {
        synchronized (lock) {
            // 循环检查队列是否已满(防止虚假唤醒)
            while (count == items.length) {
                // 释放锁并进入等待状态
                lock.wait();
            }
            
            // 插入元素到当前位置
            items[putIndex] = item;
            // 更新插入位置(循环处理)
            if (++putIndex == items.length) putIndex = 0;
            // 元素计数增加
            count++;
            
            // 唤醒所有等待线程(可能有消费者在等待)
            lock.notifyAll();
        }
    }

    /**
     * 从队列取出元素(队列空时阻塞等待)
     * @return 取出的元素
     * @throws InterruptedException 等待时被中断抛出
     */
    public T take() throws InterruptedException {
        synchronized (lock) {
            // 循环检查队列是否为空(防止虚假唤醒)
            while (count == 0) {
                // 释放锁并进入等待状态
                lock.wait();
            }
            
            // 获取当前取出位置的元素
            T item = (T) items[takeIndex];
            // 更新取出位置(循环处理)
            if (++takeIndex == items.length) takeIndex = 0;
            // 元素计数减少
            count--;
            
            // 唤醒所有等待线程(可能有生产者在等待)
            lock.notifyAll();
            
            return item;
        }
    }
}

定时器

定时器是什么

定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定 好的代码. 

时间轮定时器 

public class SimpleTimer {
    private final PriorityBlockingQueue<Task> queue = 
        new PriorityBlockingQueue<>(Comparator.comparingLong(t -> t.execTime));
    private final Thread worker;

    public SimpleTimer() {
        worker = new Thread(() -> {
            while (true) {
                try {
                    Task task = queue.take();
                    long curr = System.currentTimeMillis();
                    if (task.execTime > curr) {
                        Thread.sleep(task.execTime - curr);
                    }
                    task.runnable.run();
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        worker.start();
    }

    public void schedule(Runnable task, long delayMs) {
        queue.put(new Task(task, System.currentTimeMillis() + delayMs));
    }

    private static class Task {
        final Runnable runnable;
        final long execTime;

        Task(Runnable runnable, long execTime) {
            this.runnable = runnable;
            this.execTime = execTime;
        }
    }
}

 使用方法​​:

SimpleTimer timer = new SimpleTimer();
timer.schedule(() -> System.out.println("5秒后执行"), 5000);

线程池

线程池是什么

虽然创建线程 / 销毁线程 的开销

想象这么一个场景:在学校附近新开了一家快递店,老板很精明,想到一个与众不同的办法来经营——店里不雇佣固定员工,而是每次有业务时,临时找一名同学来送快递,送完立即解雇。这就像我们处理任务时“来一个任务,起一个线程”的模式。

然而,老板很快发现问题:每次“招聘+解雇”同学的成本极高。善于变通的老板恍然大悟,明白了为何其他公司都选择长期雇人。于是他制定新规则:将公司业务人员逐步扩张到3人,并根据业务需求灵活调整。

具体运作流程如下:当新业务到来时,老板先检查现有员工数量。若未满3人,则立即雇佣一人送快递;若已达3人,则将业务记录在任务本上,等待现有快递员空闲时处理。这正是线程池的核心模式。

线程池最大的好处就是减少每次启动、销毁线程的损耗

核心线程池实现 

public class SimpleThreadPool {
    private final BlockingQueue<Runnable> taskQueue;
    private final List<Worker> workers = new ArrayList<>();
    
    public SimpleThreadPool(int coreSize) {
        taskQueue = new LinkedBlockingQueue<>();
        for (int i = 0; i < coreSize; i++) {
            Worker worker = new Worker();
            workers.add(worker);
            worker.start();
        }
    }

    public void execute(Runnable task) {
        taskQueue.offer(task);
    }

    private class Worker extends Thread {
        public void run() {
            while (!isInterrupted()) {
                try {
                    Runnable task = taskQueue.take();
                    task.run();
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    }
}

 使用方法​​:

SimpleThreadPool pool = new SimpleThreadPool(4);
pool.execute(() -> System.out.println("执行任务"));

总结-保证线程安全的思路

使用没有共享资源的模型

适用共享资源只读,不写的模型

  • 不需要写共享资源的模型
  •  使用不可变对象

 直面线程安全(重点)

  • 保证原子性
  • 保证顺序性
  • 保证可见性

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

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

相关文章

DB-GPT支持mcp协议配置说明

简介 在 DB-GPT 中使用 MCP&#xff08;Model Context Protocol&#xff09;协议&#xff0c;主要通过配置 MCP 服务器和智能体协作实现外部工具集成与数据交互。 开启mcp服务&#xff0c;这里以网页抓取为例 npx -y supergateway --stdio "uvx mcp-server-fetch" …

CoT-Drive:利用 LLM 和思维链提示实现自动驾驶的高效运动预测

25年3月来自澳门大学和 MIT 的论文“CoT-Drive: Efficient Motion Forecasting for Autonomous Driving with LLMs and Chain-of-Thought Prompting”。 准确的运动预测对于安全的自动驾驶 (AD) 至关重要。本研究提出 CoT-Drive&#xff0c;这是一种利用大语言模型 (LLM) 和思…

Flowable7.x学习笔记(十)分页查询已部署 BPMN XML 流程

前言 上一篇文章我们已经完成了流程的部署功能&#xff0c;那么下一步就是要激活流程了&#xff0c;但是我们要需要明确的指定具体要激活部署后的哪一条流程&#xff0c;所以我们先把已部署的基础信息以及具体定义信息分页查询出来&#xff0c;本文先把基础代码生成以及完成分页…

Office文档图片批量提取工具

Office.Files.Images 是一款专注于从 Word、Excel、PPT 等 Office 文档中批量提取图片的轻量级工具&#xff0c;支持 .docx、.xlsx、.pptx 格式文件。该软件体积仅 ‌343KB‌&#xff0c;无需安装即可运行&#xff0c;通过拖拽操作实现快速解析与导出&#xff0c;尤其适合需批量…

33-公交车司机管理系统

技术&#xff1a; 基于 B/S 架构 SpringBootMySQLvueelementui 环境&#xff1a; Idea mysql maven jdk1.8 node 用户端功能 1.首页:展示车辆信息及车辆位置和线路信息 2.模块:车辆信息及车辆位置和线路信息 3.公告、论坛 4.在线留言 5.个人中心:修改个人信息 司机端功能…

PyCharm 初级教程:从安装到第一个 Python 项目

作为 Python 程序员&#xff0c;无论是刚入门还是工作多年&#xff0c;PyCharm 都是一个绕不开的开发工具。它是 JetBrains 出品的一款强大的 Python IDE&#xff0c;有自动补全、调试、虚拟环境支持、代码检查等等功能&#xff0c;体验比命令行 记事本舒服一百倍。 今天这篇…

QML FontDialog:使用FontDialog实现字体选择功能

目录 引言相关阅读FontDialog基本介绍字体属性 实例演示项目结构代码实现Main.qmlmain.cpp 代码解析运行效果 总结 引言 在桌面应用程序开发中&#xff0c;字体选择是一个常见的需求。Qt Quick提供了FontDialog组件来实现这一功能。本文将介绍如何在Qt Quick应用程序中使用Fon…

力扣刷题Day 27:环形链表(141)

1.题目描述 2.思路 创建一个结点集合&#xff0c;遍历链表&#xff0c;如果遇到已经加进集合的结点就说明链表有环。 3.代码&#xff08;Python3&#xff09; class Solution:def hasCycle(self, head: Optional[ListNode]) -> bool:node headnode_set set()while node…

研发效率破局之道阅读总结(3)工程优化

研发效率破局之道阅读总结(3)工程优化 Author: Once Day Date: 2025年4月22日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 程序的艺术_Once-Day…

metasploit(2)生成dll木马

声明&#xff01;本文章所有的工具分享仅仅只是供大家学习交流为主&#xff0c;切勿用于非法用途&#xff0c;如有任何触犯法律的行为&#xff0c;均与本人及团队无关&#xff01;&#xff01;&#xff01; 一、dll文件基本概念 DLL 是一种包含可由多个程序同时使用的代码和数…

数据结构--并查集-高效处理连通性问题

目录 一、理论基础 &#xff08;1&#xff09;并查集的功能及实现原理 &#xff08;2&#xff09;代码模版 &#xff08;3&#xff09;模拟过程 &#xff08;4&#xff09;应用 二、基础题练习 &#xff08;1&#xff09;寻找存在的路径&#xff08;模版题&#xff09; …

WPS Office安卓版云文档同步速度与PDF转换体验测评

WPS Office安卓版是很多人常用的移动办公软件。它支持在线编辑、文档同步、格式转换等功能&#xff0c;适合手机和平板用户随时处理文档。我们用它配合谷歌浏览器打开网页文档时&#xff0c;也可以将内容快速保存到云端或转换成PDF格式使用。 先说云文档同步。在打开WPS Office…

Eureka、LoadBalance和Nacos

Eureka、LoadBalance和Nacos 一.Eureka引入1.注册中心2.CAP理论3.常见的注册中心 二.Eureka介绍1.搭建Eureka Server 注册中心2.搭建服务注册3.服务发现 三.负载均衡LoadBalance1.问题引入2.服务端负载均衡3.客户端负载均衡4.Spring Cloud LoadBalancer1).快速上手2)负载均衡策…

【Linux网络】构建基于UDP的简单聊天室系统

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

【每天一个知识点】大模型的幻觉问题

“大模型的幻觉问题”是指大语言模型&#xff08;如GPT系列、BERT衍生模型等&#xff09;在生成内容时&#xff0c;产生不符合事实或逻辑的虚假信息&#xff0c;即所谓的“幻觉”&#xff08;hallucination&#xff09;。这在诸如问答、摘要、翻译、代码生成等任务中尤其常见。…

[大模型]什么是function calling?

什么是function calling&#xff1f; 大模型的 ​​Function Calling​​&#xff08;函数调用&#xff09;是一种让大语言模型&#xff08;如 GPT、Claude 等&#xff09;与外部工具、API 或自定义函数交互的机制。 它的核心目的是让模型能够根据用户的需求&#xff0c;​​…

Java高频面试之并发编程-05

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;线程有哪些调度方法&#xff1f; 在Java中&#xff0c;线程的调用方法主要包括以下几种方式&#xff0c;每种方式适用于…

野外价值观:在真实世界的语言模型互动中发现并分析价值观

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

【Linux】47.高级IO(1)

文章目录 1. 高级IO1.1 五种IO模型1.2 高级IO重要概念1.2.1 同步通信 vs 异步通信1.2.2 阻塞 vs 非阻塞 1.3非阻塞IO1.3.1 fcntl1.3.2 实现函数SetNoBlock1.3.3 轮询方式读取标准输入1.3.4 I/O多路转接之select1.3.4.1 初识select&#xff1a;1.3.4.2 select函数原型1.3.4.3 理…

notepad++技巧:查找和替换:扩展 or 正则表达式

notepad 有很多优点&#xff1a;多标签&#xff0c;代码高亮&#xff0c;我最喜欢的是查找和替换。 除了可以一次性查找所有打开文件&#xff0c;还可以使用 扩展 or 正则表达式。 例如&#xff1a; 去掉空行&#xff1a;正则表达式&#xff1a; ^\s*$\r\n ^ 表示行首。\s*…