线程、线程池、锁

线程概念

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

线程生命周期

  1. 新建(New)‌:线程对象被创建但尚未启动 Thread thread = new Thread();
  2. 就绪(Runnable)‌:线程已准备好运行,等待CPU调度,start()只能调用一次,多次调用会抛出IllegalThreadStateException
  3. 运行(Running)‌:线程正在执行 cup执行
  4. 阻塞(Blocked)‌:线程等待某个条件(如I/O操作、锁等)
  5. 等待:await(),sleep(),join()
  6. 终止(Terminated)‌:线程执行完毕或异常退出

线程的 start 方法和 run 方法有什么区别?

public class Demo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /**
         * 1.直接重写run() 或继承Thread类再重写run()
         */
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("Thread");
            }
        };
        // 开启线程
        thread.start();

        /**
         * 2.lambda、内部类或线程类方式实现Runnable接口,实现run()方法
         * 再交给Thread 类
         */
        Thread runThread = new Thread(() -> {
            System.out.println("Runnable");
        });
        // 开启线程
        runThread.start();

        /**
         * 3.lambda、内部类或线程类方式实现Callable接口,实现call()方法
         * 再交给Thread 类:FutureTask本质也是Runnable实现类
         */
        FutureTask<String> futureTask = new FutureTask<String>(() -> {
            System.out.println("Callable");
            return "CallableThread";
        });
        Thread callThread = new Thread(futureTask);
        // 开启线程
        callThread.start();
        // 获取call()方法的返回值
        String s = futureTask.get();
        System.out.println("call()方法的返回值:"+s);
    }

}

线程实现方式

  1. 用户级线程‌:由用户空间的线程库实现

    • 优点:不依赖操作系统,切换快
    • 缺点:一个线程阻塞会导致整个进程阻塞

public class UserLevelThread {
    public static void main(String[] args) {
        // 模拟用户级线程 - 实际Java不直接支持纯用户级线程
        Runnable task1 = () -> System.out.println("用户线程1运行");
        Runnable task2 = () -> System.out.println("用户线程2运行");
        
        // 在单内核线程上顺序执行
        task1.run();
        task2.run();
    }
}
  1. 内核级线程‌:由操作系统内核直接支持

    • 优点:一个线程阻塞不会影响其他线程
    • 缺点:创建和切换需要系统调用,开销较大

public class KernelThread {
    public static void main(String[] args) {
        // 真实内核线程
        Thread t1 = new Thread(() -> {
            System.out.println("内核线程1 ID: " + Thread.currentThread().getId());
        });
        
        Thread t2 = new Thread(() -> {
            System.out.println("内核线程2 ID: " + Thread.currentThread().getId());
        });
        
        t1.start();
        t2.start();
    }
}
  1. 混合实现‌:结合两者优点(如Java的线程模型)


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HybridModel {
    public static void main(String[] args) {
        // 线程池实现混合模型
        ExecutorService pool = Executors.newFixedThreadPool(2);
        
        // 提交多个任务
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            pool.execute(() -> {
                System.out.println("任务" + taskId + "在" + 
                    Thread.currentThread().getName() + "执行");
            });
        }
        pool.shutdown();
    }
}

ThreadLocal

1. 一句话总结

ThreadLocal 就像给‌每个线程发了一个专属的储物柜‌,每个线程只能存取自己的东西,互相看不到对方柜子里的内容。


2. 生活化比喻
  • 场景‌:公司更衣室
    • 每个员工(线程)有自己独立的柜子(ThreadLocal)。
    • 员工A 往自己柜子里放了一双鞋,员工B 完全不知道,也拿不到。
    • 员工离职(线程结束)时,柜子会被清空(避免内存泄漏)。

3. 解决了什么问题?
  • 问题‌:多线程共享变量时,会打架(线程不安全)。
    • 比如:10个线程同时修改同一个变量 count++,结果可能错乱。
  • ThreadLocal 方案‌:
    • 直接给每个线程发一个独立的 count 副本,各自玩自己的,互不干扰。

4. 代码例子(秒懂版)
public class ThreadLocalDemo {
    // 1. 创建一个ThreadLocal(相当于发储物柜)
    private static ThreadLocal<String> name = new ThreadLocal<>();

    public static void main(String[] args) {
        // 线程1:存自己的名字
        new Thread(() -> {
            name.set("张三"); // 存到自己的柜子
            System.out.println("线程1的名字:" + name.get()); // 只能拿到"张三"
        }).start();

        // 线程2:存自己的名字
        new Thread(() -> {
            name.set("李四"); // 存到自己的柜子
            System.out.println("线程2的名字:" + name.get()); // 只能拿到"李四"
        }).start();
    }
}

核心特点‌:

  • 线程隔离:变量对每个线程独立可见
  • 无锁化:天然避免线程安全问题
2. ‌底层原理
  • 数据结构‌:每个线程(Thread类)内部维护一个ThreadLocalMap(类似HashMap),键为ThreadLocal实例,值为存储的变量。
  • 哈希冲突解决‌:开放地址法(线性探测)。
3. ‌核心方法
方法作用
get()获取当前线程的变量副本
set(T value)设置当前线程的变量副本
remove()移除当前线程的变量副本(防止内存泄漏)
initialValue()覆盖此方法可定义初始值(默认返回null
4. ‌内存泄漏问题
  • 原因‌:
    • ThreadLocalMap的键是弱引用(WeakReference<ThreadLocal>),但值是强引用。
    • ThreadLocal实例被回收,但线程未终止,会导致value无法被回收。
  • 解决方案‌:
    • 显式调用remove()清理条目。
    • 使用static final修饰ThreadLocal实例(延长生命周期)。
5. ‌典型应用场景
  • 数据库连接管理‌:每个线程维护独立的Connection(如 Spring 的TransactionSynchronizationManager)。
  • 用户会话信息‌:存储当前请求的用户身份(如 Spring Security 的SecurityContextHolder)。
  • 日期格式化‌:避免SimpleDateFormat的线程不安全问题。
6. ‌与同步机制对比
特性ThreadLocalsynchronized/Lock
数据隔离线程独享共享数据+同步访问
性能无锁,更高性能有锁,可能阻塞
适用场景线程间数据隔离线程间数据共享

Volitale

volatile 是 Java 中用于修饰变量的关键字,主要解决多线程环境下的‌内存可见性‌和‌指令重排序‌问题。
核心特性‌:

  • 可见性‌:保证变量修改后立即对其他线程可见
  • 有序性‌:禁止指令重排序优化
2. ‌底层原理
  • 内存屏障(Memory Barrier)‌:
    • 写操作:强制将工作内存中的修改刷新到主内存
    • 读操作:强制从主内存重新加载变量值
  • JMM(Java Memory Model)‌:遵循 happens-before 规则,确保多线程操作的顺序性
3. ‌适用场景
场景说明
状态标志位如 while (!stop) 循环退出控制
单例模式(DCL)双重检查锁定中修饰实例变量(防止指令重排序)
多线程共享变量简单共享变量(不保证原子性,如 i++ 仍需同步)
4. ‌与 synchronized 对比
特性volatilesynchronized
原子性不保证(如 i++保证
阻塞非阻塞阻塞
性能更高(无锁)较低(上下文切换开销)
作用范围仅修饰变量修饰方法/代码块
5. ‌代码示例
public class VolatileDemo {
    private volatile boolean flag = false;

    public void start() {
        new Thread(() -> {
            while (!flag) {
                // 循环直到 flag 变为 true
            }
            System.out.println("Thread stopped by flag");
        }).start();

        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true; // 修改 flag 值
            System.out.println("Flag set to true");
        }).start();
    }

    public static void main(String[] args) {
        new VolatileDemo().start();
    }
}

输出‌:

Flag set to true

Thread stopped by flag

6. ‌注意事项
  • 不保证原子性‌:复合操作(如 count++)仍需使用 synchronized 或 AtomicInteger
  • 性能影响‌:频繁读写 volatile 变量会强制内存同步,可能降低性能
  • 替代方案‌:
    • 原子类(AtomicInteger 等)
    • final 不可变对象
7. ‌常见误区
  • 误区1‌:认为 volatile 能替代锁(实际仅解决可见性和有序性)
  • 误区2‌:滥用 volatile 导致伪共享(False Sharing)问题
8. ‌扩展:DCL 单例模式
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;
    }
}

线程池的概念

线程池是一种多线程处理形式,它预先创建一组线程并管理它们的生命周期,避免了频繁创建和销毁线程的开销。线程池的核心思想是‌线程复用‌,通过维护一个线程队列来执行多个任务。

线程池分类

java.util.concurrent包下的Excutor类创建线程池

Executors.newSingleThreadExecutor();

Executors.newScheduledThreadPool(int corePoolSize);

Executors.newCachedThreadPool();

Executors.newFixedThreadPool(int nThreads);

Spring的封装线程池

@Bean(name = "")
    public ThreadPoolTaskExecutor readPoolTaskExecutor() {
        ThreadPoolTaskExecutor billDeductExecutor = new ThreadPoolTaskExecutor();
        //核心线程数目 ,cpu个数 * 2
        billDeductExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2);
        //指定最大线程数
        billDeductExecutor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
        //队列中最大的数目
        billDeductExecutor.setQueueCapacity(128);
        //线程空闲后的最大存活时间,单位:s
        billDeductExecutor.setKeepAliveSeconds(60);
        // 线程池对拒绝任务(无线程可用)的处理策略
        billDeductExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //线程名称前缀
        billDeductExecutor.setThreadNamePrefix("OperatorBillThreadPool-");
        return billDeductExecutor;
    }

定时任务线程池(ScheduledThreadPool)

/**
     * 执行周期性或定时任务
     */
    @Bean(name = "scheduledExecutorService")
    protected ScheduledExecutorService scheduledExecutorService() {
        return new ScheduledThreadPoolExecutor(corePoolSize,
                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build()) {
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);
                Threads.printException(r, t);
            }
        };
    }


/**
     * 任务是否执行心跳维持任务
     *
     * @param operatorCode 运营商编码
     * @return 任务
     */
    private ScheduledFuture<?> keepAlive(String operatorCode) {
        String hbKey = OPERATOR_STATE_COMPARE_KEY + operatorCode + ":hearBeat";
        return scheduledExecutorService.scheduleAtFixedRate(() -> {
            log.error("OrderOperatorStaCmpTask|operatorCode={}|keepAlive", operatorCode);
            redisCache.setCacheObject(hbKey, "1", 32L, TimeUnit.SECONDS);
        }, 0L, 30L, TimeUnit.SECONDS);
    }

分治任务计算线程池

@Bean(name = "billCalculateThreadPool")
	public ForkJoinPool billCalculateThreadPool() {
		return new ForkJoinPool(10);
	}

ForkJoinPool的核心特点

ForkJoinPool是Java 7引入的一种特殊线程池,专为‌分治算法‌和‌递归任务‌设计,采用‌工作窃取(work-stealing)‌算法实现高效并行计算。

与普通线程池(ThreadPoolExecutor)的主要区别

特性ForkJoinPoolThreadPoolExecutor
设计目标处理可分解的并行任务处理独立任务
任务队列每个线程有自己的双端队列共享的任务队列
任务调度工作窃取算法先进先出(FIFO)
任务类型ForkJoinTask(RecursiveAction/RecursiveTask)Runnable/Callable
适用场景递归/分治任务(如归并排序)常规异步任务

ForkJoinPool的优势

  1. 工作窃取算法

    • 每个线程维护自己的任务队列

    • 空闲线程可以从其他线程队列"窃取"任务

    • 减少线程竞争,提高CPU利用率

  2. 更适合递归任务

    • 自动处理任务分解和结果合并

    • 简化分治算法的并行实现

  3. 更优的负载均衡

    • 动态平衡各线程的工作量

    • 避免某些线程空闲而其他线程过载

  4. 减少同步开销

    • 本地队列操作减少锁竞争

    • 适合计算密集型任务

ForkJoinPool的局限性

  1. 不适合I/O密集型任务

    • 设计初衷是CPU密集型计算

    • 线程阻塞会降低工作窃取效率

  2. 任务分解需要成本

    • 小任务分解可能得不偿失

    • 需要合理设置阈值

  3. 调试复杂度高

    • 递归/并行执行路径难以跟踪

    • 异常处理更复杂

  4. 内存消耗较大

    • 每个线程维护独立队列

    • 任务对象较多时占用更多内存

使用场景建议

适合使用ForkJoinPool的情况‌:

  • 递归算法(如快速排序、归并排序)

  • 可分解的大规模计算任务

  • 需要自动任务分解/结果合并的场景

  • CPU密集型并行计算

适合使用ThreadPoolExecutor的情况‌:

  • 独立任务处理(如Web请求)

  • I/O密集型操作

  • 不需要任务分解的常规异步任务

  • 需要更精细控制线程行为的场景

性能对比示例

对于计算斐波那契数列:

  • ForkJoinPool在n>30时优势明显

  • ThreadPoolExecutor在小规模计算(n<20)时更高效

对于文件处理:

  • ThreadPoolExecutor在I/O操作上表现更好

  • ForkJoinPool可能因线程阻塞而性能下降

自定义线程池创建

更灵活的方式是直接使用ThreadPoolExecutor构造函数:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler
);

参数详解

  1. corePoolSize‌ (核心线程数)

    • 线程池中保持的最小线程数
    • 即使线程空闲也不会被回收(除非设置allowCoreThreadTimeOut)
  2. maximumPoolSize‌ (最大线程数)

    • 线程池允许的最大线程数
    • 当工作队列满时,会创建新线程直到达到此值
  3. keepAliveTime‌ (线程空闲时间)

    • 非核心线程的空闲存活时间
    • 超过此时间且线程数大于corePoolSize时,线程会被回收
  4. unit‌ (时间单位)

    • keepAliveTime的时间单位(TimeUnit.SECONDS等)
  5. workQueue‌ (工作队列)

    • 保存待执行任务的阻塞队列
    • 常见实现:
      • ArrayBlockingQueue: 有界队列
      • LinkedBlockingQueue: 无界队列
      • SynchronousQueue: 不存储元素的队列
  6. threadFactory‌ (线程工厂)

    • 用于创建新线程
    • 可以自定义线程名称、优先级等
  7. rejectedExecutionHandler‌ (拒绝策略)

    • 当线程池和工作队列都满时的处理策略
    • 内置策略:
      • AbortPolicy: 抛出RejectedExecutionException(默认)
      • CallerRunsPolicy: 由调用线程执行该任务
      • DiscardPolicy: 直接丢弃任务
      • DiscardOldestPolicy: 丢弃队列中最老的任务

线程池工作流程

  1. 提交任务时,如果当前线程数 < corePoolSize,创建新线程执行任务
  2. 如果线程数 ≥ corePoolSize,将任务放入工作队列
  3. 如果队列已满且线程数 < maximumPoolSize,创建新线程执行任务
  4. 如果队列已满且线程数 = maximumPoolSize,执行拒绝策略

最佳实践

  1. 根据任务类型选择合适线程池
  2. 合理设置核心和最大线程数
  3. 选择合适的队列容量
  4. 为线程池设置有意义的名称(通过ThreadFactory)
  5. 考虑使用自定义拒绝策略
  6. 监控线程池运行状态

线程池应用队列

1. ThreadPoolExecutor 队列选择

队列类型特点适用场景
LinkedBlockingQueue无界队列(默认),任务积压可能导致OOM任务量可控的短时异步任务
ArrayBlockingQueue有界队列,固定容量,队列满时触发拒绝策略需要限制资源消耗的稳定流量场景
SynchronousQueue直接传递队列,无缓冲,任务立即交给空闲线程或新建线程高吞吐量、瞬时高并发任务
PriorityBlockingQueue优先级队列,任务按优先级排序需要任务优先级调度的场景

推荐组合‌:

  • newFixedThreadPool + LinkedBlockingQueue(默认)
  • newCachedThreadPool + SynchronousQueue(默认)

2. ScheduledThreadPoolExecutor 队列

队列类型特点必要性
DelayedWorkQueue内部专用延迟队列,按任务触发时间排序必须使用(强制内置实现)

注意‌:无法自定义队列,所有定时任务均依赖此队列。


3. ForkJoinPool 队列

队列类型特点必要性
Work-Stealing队列每个线程维护双端队列,空闲线程可窃取其他队列任务必须使用(强制内置实现)

优势‌:自动负载均衡,适合递归分治任务。


4. 特殊场景队列选择

场景推荐队列原因
高吞吐+低延迟SynchronousQueue避免任务排队,直接分配线程
资源严格限制ArrayBlockingQueue + 拒绝策略防止任务无限堆积导致OOM
任务优先级差异大PriorityBlockingQueue确保高优先级任务优先执行
批量任务处理LinkedBlockingQueue缓冲任务,平衡生产者和消费者速度

5. 拒绝策略搭配建议

选择队列时需同步考虑拒绝策略(如AbortPolicyCallerRunsPolicy等),例如:

  • ArrayBlockingQueue‌ + AbortPolicy:快速失败,保护系统稳定性
  • LinkedBlockingQueue‌ + CallerRunsPolicy:降级为同步执行,避免丢失任务

总结

  • 常规任务‌:优先选LinkedBlockingQueue(平衡性最佳)
  • 定时任务‌:强制使用DelayedWorkQueue
  • 分治任务‌:强制使用Work-Stealing队列
  • 极端场景‌:根据吞吐量/资源限制选择SynchronousQueueArrayBlockingQueue

合理搭配队列与线程池参数(核心线程数、最大线程数等)才能最大化性能。

锁的分类

1. 按线程竞争策略分类
类型特点适用场景
悲观锁默认会发生冲突,操作前先加锁(如synchronizedReentrantLock写多读少、冲突概率高的场景
乐观锁假设不会冲突,提交时检查版本(如CASAtomicInteger读多写少、冲突概率低的场景

2. 按资源共享策略分类
类型特点示例
独占锁(排他锁)同一时刻只允许一个线程访问(如ReentrantLock数据库行锁、文件写入锁
共享锁允许多个线程同时读取(如ReentrantReadWriteLock.ReadLock数据库表级锁、缓存读锁

3. 按锁的实现方式分类
类型实现原理
内置锁JVM 原生支持(synchronized关键字)
显式锁JDK 提供的锁接口(Lock接口,如ReentrantLock
分布式锁跨进程协调(如 Redis 的 SETNX、ZooKeeper 临时节点)
自旋锁线程循环尝试获取锁(如AtomicBooleancompareAndSet

4. 按锁的公平性分类
类型特点示例
公平锁按请求顺序分配锁(防止线程饥饿)new ReentrantLock(true)
非公平锁允许插队(性能更高,但可能造成饥饿)synchronizedReentrantLock()

5. 按锁的重入性分类
类型特点示例
可重入锁同一线程可重复获取锁(防止死锁)synchronizedReentrantLock
不可重入锁线程重复获取会阻塞自身(易死锁)自定义简单锁

6. 按锁的粒度分类
类型特点示例
偏向锁无竞争时消除同步开销(JVM 优化)JDK6+ 默认开启
轻量级锁短时竞争时用CAS替代阻塞JVM 自动升级
重量级锁线程竞争激烈时,通过操作系统互斥实现synchronized 最终状态

7. 特殊锁类型
类型特点使用场景
分段锁将数据分段加锁(如ConcurrentHashMap高并发集合类
读写锁读共享,写互斥(如ReentrantReadWriteLock读多写少场景(缓存)
邮戳锁乐观读锁(StampedLock)极高性能读场景
条件锁基于条件阻塞(如Condition.await()生产者消费者模型

8. 锁的升级过程(JVM 优化)

mermaidCopy Code

graph LR A[无锁] -->|首次访问| B[偏向锁] B -->|发生竞争| C[轻量级锁] C -->|竞争加剧| D[重量级锁]


9. 选锁黄金法则
  1. 简单场景‌ → 优先用 synchronized(自动释放、JVM 优化)
  2. 高性能需求‌ → 考虑 ReentrantLock + CAS(可控性强)
  3. 读多写少‌ → 选择 ReadWriteLock 或 StampedLock
  4. 分布式环境‌ → Redis/ZooKeeper 分布式锁
  5. 锁细化‌ → 分段锁或并发集合类

💡 ‌锁性能口诀‌:
无锁 > 偏向锁 > 轻量级锁 > 重量级锁
乐观锁 > 悲观锁
读锁 > 写锁


10. 经典锁对比表
特性synchronizedReentrantLockReadWriteLock
锁获取方式JVM 隐式获取代码显式控制显式控制读/写锁
可中断✅ (lockInterruptibly)
公平锁✅ (可配置)✅ (可配置)
条件等待单一 wait()/notify()多 Condition支持
性能JDK6+ 大幅优化高竞争下更优读多写少场景最优
锁释放自动释放必须手动 unlock()手动释放

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值