Java 多线程编程

张开发
2026/4/15 0:14:34 15 分钟阅读

分享文章

Java 多线程编程
Java 多线程编程 (Java Concurrency) 深度学习笔记1. 基础概念1.1 进程 vs 线程进程 (Process)操作系统进行资源分配和调度的基本单位。拥有独立的内存空间堆、栈、方法区等。线程 (Thread)CPU 调度和执行的基本单位。一个进程可以包含多个线程线程共享进程的堆和方法区但拥有独立的栈和程序计数器。优势多线程可以充分利用多核 CPU提高程序响应速度如 GUI 程序不卡顿提高资源利用率。1.2 创建线程的三种方式继承Thread类缺点Java 单继承限制了扩展性。实现Runnable接口推荐优点解耦可以继承其他类。缺点无法直接获取返回值无法抛出受检异常。实现Callable接口配合FutureTask优点有返回值可抛出异常。适用需要获取执行结果的场景。// 方式 1: RunnableclassMyTaskimplementsRunnable{publicvoidrun(){System.out.println(Runnable 执行中...);}}newThread(newMyTask()).start();// 方式 2: Callable FutureTaskCallableStringtask()-{returnCallable 执行结果;};FutureTaskStringfuturenewFutureTask(task);newThread(future).start();System.out.println(future.get());// 阻塞获取结果1.3 线程生命周期 (6 种状态)Java 线程状态定义在Thread.State枚举中NEW新建未调用start()。RUNNABLE可运行包含操作系统中的 Running 和 Ready 状态。BLOCKED等待获取监视器锁synchronized。WAITING无限期等待Object.wait(),Thread.join(),LockSupport.park()。TIMED_WAITING限期等待Thread.sleep(ms),Object.wait(ms)。TERMINATED执行结束。2. 核心 API 与操作2.1 常用方法方法作用注意事项start()启动线程JVM 调用run()不可重复调用否则抛IllegalThreadStateExceptionrun()线程执行体直接调用run()只是普通方法调用不会启动新线程sleep(ms)让当前线程休眠不释放锁属于Thread静态方法join()等待线程结束当前线程阻塞直到目标线程结束yield()让出 CPU提示调度器当前线程愿意让出但不保证立即让出interrupt()中断线程设置中断标志位需配合isInterrupted()或捕获InterruptedException处理setDaemon(true)设为守护线程守护线程随用户线程结束而结束如 GC 线程2.2 线程安全与可见性原子性 (Atomicity)操作不可分割。问题i不是原子操作读-改-写。解决synchronized,Lock,AtomicInteger。可见性 (Visibility)一个线程修改了变量其他线程立即可见。问题CPU 缓存导致。解决volatile,synchronized,Lock。有序性 (Ordering)指令重排序问题。解决volatile(禁止重排序),happens-before原则。3. 锁机制 (Locking)3.1synchronized(内置锁)用法修饰实例方法锁当前对象 (this)。修饰静态方法锁当前类的Class对象。修饰代码块锁指定对象。特点JVM 层面实现自动加锁/释放。JDK 1.6 后优化偏向锁 - 轻量级锁 (自旋) - 重量级锁。不可中断非公平。publicsynchronizedvoidsyncMethod(){}publicvoidsyncBlock(){synchronized(this){}}3.2ReentrantLock(显式锁)特点java.util.concurrent.locks.Lock接口实现。手动加锁 (lock()) 和释放 (unlock())必须在finally中释放。支持公平锁和非公平锁。支持可中断、超时尝试(tryLock)。支持多个Condition对象实现精确唤醒。ReentrantLocklocknewReentrantLock();lock.lock();try{// 临界区}finally{lock.unlock();// 必须释放}3.3ReadWriteLock与ReentrantReadWriteLock场景读多写少。机制读锁 (readLock)共享锁多个线程可同时读。写锁 (writeLock)独占锁写时互斥。注意写锁可降级为读锁但读锁不能升级为写锁。3.4StampedLock(JDK 8 新增)特点性能更高支持乐观读(Optimistic Reading)。机制读操作不阻塞写先假设无写最后验证版本号。如果验证失败转为悲观读。注意不可重入不支持 Condition。4. 并发工具类 (java.util.concurrent)4.1CountDownLatch(倒计时门闩)作用一个线程等待其他 N 个线程完成。场景主线程等待所有子线程计算完成。核心方法countDown()(减 1),await()(等待)。特点一次性使用。4.2CyclicBarrier(循环栅栏)作用N 个线程互相等待到达屏障点后一起执行。场景多线程并行计算最后汇总结果。特点可重用循环。4.3Semaphore(信号量)作用控制同时访问特定资源的线程数量。场景限流、数据库连接池。核心方法acquire(),release().4.4Exchanger作用两个线程在屏障点交换数据。5. 线程池 (Thread Pool)5.1 为什么使用线程池降低资源消耗复用线程。提高响应速度无需创建线程。提高可管理性统一分配、监控、调优。5.2ThreadPoolExecutor核心参数publicThreadPoolExecutor(intcorePoolSize,// 核心线程数常驻intmaximumPoolSize,// 最大线程数longkeepAliveTime,// 非核心线程空闲存活时间TimeUnitunit,// 时间单位BlockingQueueRunnableworkQueue,// 任务队列ThreadFactorythreadFactory,// 线程工厂命名等RejectedExecutionHandlerhandler// 拒绝策略)5.3 工作流程任务提交 - 核心线程数未满 -创建核心线程执行。核心线程满 - 队列未满 -放入队列。队列满 - 最大线程数未满 -创建非核心线程执行。最大线程数满 -触发拒绝策略。5.4 拒绝策略 (RejectedExecutionHandler)AbortPolicy(默认)抛出RejectedExecutionException。CallerRunsPolicy由调用线程提交任务的线程执行该任务降低提交速度。DiscardPolicy直接丢弃不抛异常。DiscardOldestPolicy丢弃队列中最老的任务重试提交。5.5 创建线程池的误区❌ 禁止使用Executors工厂类(newFixedThreadPool,newCachedThreadPool)原因FixedThreadPool和SingleThreadPool队列容量为Integer.MAX_VALUE可能 OOM。原因CachedThreadPool最大线程数为Integer.MAX_VALUE可能 OOM。✅ 推荐手动new ThreadPoolExecutor明确参数。// 推荐写法ThreadPoolExecutorexecutornewThreadPoolExecutor(5,10,60L,TimeUnit.SECONDS,newArrayBlockingQueue(100),Executors.defaultThreadFactory(),newThreadPoolExecutor.CallerRunsPolicy());6. 并发容器 (Concurrent Collections)6.1ConcurrentHashMap(CHM)JDK 1.7分段锁 (Segment)锁粒度为段。JDK 1.8NodeCASsynchronized。锁粒度更细锁住链表/红黑树的头节点。性能大幅提升。特点线程安全不支持null键值。6.2CopyOnWriteArrayList原理写时复制。写操作时复制新数组修改后替换引用。读操作无锁。场景读多写少如监听器列表。缺点内存占用大写操作性能低数据最终一致性非实时。6.3BlockingQueue(阻塞队列)作用生产者 - 消费者模型的核心。实现ArrayBlockingQueue数组结构有界。LinkedBlockingQueue链表结构可选有界默认 Integer.MAX_VALUE。PriorityBlockingQueue优先级队列。SynchronousQueue不存储元素直接移交。7. 原子类 (Atomic Variables)基于CAS (Compare-And-Swap)算法实现无锁高效。基本类型AtomicInteger,AtomicLong,AtomicBoolean。数组类型AtomicIntegerArray。引用类型AtomicReference。字段更新器AtomicIntegerFieldUpdater。累加器LongAdder(高并发下性能优于AtomicLong通过分段累加减少竞争)。CAS 问题ABA 问题值从 A-B-ACAS 认为没变。解决AtomicStampedReference(加版本号)。循环时间长开销大竞争激烈时自旋消耗 CPU。只能保证一个变量原子性多个变量需配合锁。8. 线程安全设计模式8.1 不可变对象 (Immutable)String,Integer,final修饰的类。线程安全无需同步。8.2 线程封闭 (Thread Confinement)栈封闭局部变量。ThreadLocal每个线程拥有独立的变量副本。注意内存泄漏问题remove()清理。场景数据库连接、Session 管理、用户上下文。8.3 不可变集合Collections.synchronizedList()(需手动同步)。CopyOnWriteArrayList。9. 常见问题与面试考点Q:sleep()和wait()的区别A:sleep是Thread方法不释放锁wait是Object方法释放锁需notify唤醒。Q:volatile能保证原子性吗A: 不能。只保证可见性和有序性。i操作仍需锁或原子类。Q:synchronized和ReentrantLock的区别A:synchronized是 JVM 层面自动释放非公平ReentrantLock是 API 层面手动释放支持公平/非公平、中断、超时。Q: 线程池参数如何设置A:CPU 密集型N 1(N 为核数)。IO 密集型2N或N / (1 - 阻塞系数)。需结合压测调整。Q:ThreadLocal内存泄漏原因A:ThreadLocalMap的 Key 是弱引用Value 是强引用。如果线程长期存活如线程池Key 被回收但 Value 还在导致泄漏。必须手动remove()。Q: 死锁产生的条件及排查A: 互斥、请求与保持、不剥夺、循环等待。排查jstack pid查看堆栈信息。10. 最佳实践与避坑指南避免手动创建线程始终使用线程池。锁的粒度尽量缩小锁的范围减少竞争。避免在锁中执行耗时操作如 IO、网络请求。使用并发容器优先使用ConcurrentHashMap等少用synchronized包装集合。异常处理线程池中的异常不会抛出到主线程需设置UncaughtExceptionHandler或在run()中捕获。优雅关闭线程池关闭使用shutdown()(等待任务完成) 或shutdownNow()(尝试中断)不要直接kill进程。慎用stop()JDK 已废弃可能导致数据不一致。使用中断机制 (interrupt)。11. 总结Java 多线程是构建高并发、高性能系统的基石。入门掌握Thread,Runnable,synchronized。进阶深入理解JUC包 (Lock,Atomic,ConcurrentHashMap, 线程池)。高阶理解AQS原理、volatile内存模型、happens-before、无锁编程。核心原则能不用锁就不用锁能用并发容器就不用手动同步能用线程池就不要手动创建线程。

更多文章