Java多线程超详学习内容
一、多线程基础概念
线程与进程:
进程是程序的一次执行过程,是系统资源分配的基本单位(如一个运行中的浏览器)。
线程是进程内的执行单元,是CPU调度的基本单位,一个进程可包含多个线程(如浏览器中同时加载页面、播放视频的线程)。
线程共享进程的内存资源(如堆、方法区),但有自己独立的栈和程序计数器。
多线程的意义:
提高程序执行效率(如并发处理多个任务,避免单线程阻塞导致的资源浪费)。
提升用户体验(如UI线程与后台数据加载线程分离,避免界面卡顿)。
线程的状态:
新建(New):线程对象创建后未启动的状态。
就绪(Runnable):调用 start() 后,线程等待CPU调度的状态(包含运行中状态)。
阻塞(Blocked):因竞争同步锁被阻塞的状态(如未获取 synchronized 锁)。
等待(Waiting):无时间限制的等待(如调用 wait() 、 join() 无参方法)。
超时等待(Timed Waiting):有时间限制的等待(如 sleep(1000) 、 wait(1000) )。
终止(Terminated):线程执行完毕或异常终止的状态。
二、线程的创建与启动
1. 继承 Thread 类
步骤:
1. 自定义类继承 Thread ,重写 run() 方法(线程执行的任务)。
2. 创建线程对象,调用 start() 方法启动线程(而非直接调用 run() ,否则为普通方法调用)。
示例:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行任务");
}
}
public class Test {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // 启动线程
}
}
2. 实现 Runnable 接口
步骤:
1. 自定义类实现 Runnable 接口,重写 run() 方法。
2. 创建 Runnable 实例,作为参数传入 Thread 构造器,调用 start() 启动。
优势:避免单继承限制,可共享资源(多个线程共享一个 Runnable 实例)。
示例:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行任务");
}
}
public class Test {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
3. 实现 Callable 与 Future
特点:可返回结果、可抛出受检异常(相比 Runnable 更灵活)。
步骤:
1. 实现 Callable<T> 接口,重写 call() 方法(返回值类型为 T )。
2. 通过 FutureTask<T> 包装 Callable 实例,作为参数传入 Thread 。
3. 调用 FutureTask.get() 获取返回结果(会阻塞当前线程直到结果返回)。
示例:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1 + 1;
}
}
public class Test {
public static void main(String[] args) throws Exception {
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
System.out.println("结果:" + task.get()); // 输出2
}
}
4. 线程池创建(推荐)
通过 ExecutorService 线程池管理线程(减少线程创建/销毁开销,控制并发数)。
常用方法: Executors.newFixedThreadPool(n) (固定大小线程池)、 newCachedThreadPool() (缓存线程池)等。
示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3); // 3个线程的线程池
pool.submit(new MyRunnable()); // 提交任务
pool.shutdown(); // 关闭线程池
}
}
三、线程的常用方法
start() :启动线程,使线程进入就绪状态(由CPU调度执行 run() )。
run() :定义线程执行的任务(直接调用仅为普通方法,不会启动新线程)。
sleep(long millis) :让当前线程休眠指定毫秒数(进入超时等待状态,不释放锁)。
wait() / notify() / notifyAll() :
用于线程间通信,需在 synchronized 代码块/方法中调用。
wait() :当前线程释放锁,进入等待状态。
notify() :唤醒一个等待该锁的线程; notifyAll() :唤醒所有等待该锁的线程。
join() :让当前线程等待调用线程执行完毕后再继续(如 t.join() ,主线程等待t执行完)。
yield() :当前线程让出CPU资源,重新进入就绪状态(可能立即被再次调度)。
interrupt() :中断线程(设置中断标志,需线程主动检测标志并处理,如退出循环)。
isAlive() :判断线程是否存活(处于就绪、运行、阻塞、等待状态时为 true )。
四、线程同步(解决线程安全问题)
1. 线程安全问题
原因:多个线程共享资源,且操作非原子性(如多线程同时修改同一变量)。
示例:两个线程同时对 count 加1,可能导致结果小于预期(因 count++ 拆分为“读取-修改-写入”三步,中间可能被其他线程打断)。
2. 同步机制
(1) synchronized 关键字
作用:保证同一时刻只有一个线程执行同步代码,实现互斥。
用法:
修饰方法: public synchronized void method() (锁为当前对象 this )。
修饰静态方法: public static synchronized void method() (锁为类的 Class 对象)。
修饰代码块: synchronized(锁对象) { ... } (锁对象可为任意对象,推荐用 this 或 Class 对象)。
原理:通过“对象监视器”(monitor)实现,线程获取锁后进入同步区域,释放锁后其他线程可竞争。
(2) Lock 接口(JDK 1.5+)
相比 synchronized 的优势:可手动获取/释放锁、可中断获取锁、可超时获取锁、支持公平锁。
常用实现类: ReentrantLock (可重入锁,与 synchronized 一样支持重入)。
用法:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
private Lock lock = new ReentrantLock();
public void method() {
lock.lock(); // 获取锁
try {
// 同步代码
} finally {
lock.unlock(); // 释放锁(必须在finally中,避免异常导致锁未释放)
}
}
}
(3)原子类( java.util.concurrent.atomic )
用于解决基本数据类型的线程安全问题,基于CAS(Compare And Swap)机制实现(无锁操作,效率高)。
常用类: AtomicInteger 、 AtomicLong 、 AtomicBoolean 等。
- 示例: AtomicInteger count = new AtomicInteger(0); count.incrementAndGet(); (原子性自增)。
(4)线程安全的集合
Vector 、 Hashtable (古老,效率低,基于 synchronized )。
Collections.synchronizedXXX() :将非线程安全集合转为线程安全(如 synchronizedList(list) )。
java.util.concurrent 包下的集合: ConcurrentHashMap 、 CopyOnWriteArrayList 等(高效,基于分段锁、写时复制等机制)。
五、线程通信
目的:协调多个线程的执行顺序(如“生产者-消费者”模型中,生产者生产后通知消费者,消费者消费完通知生产者)。
实现方式:
wait() / notify() / notifyAll() (结合 synchronized )。
Condition (结合 Lock ,更灵活,可创建多个条件):
Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition(); // 非空条件
Condition notFull = lock.newCondition(); // 非满条件
// 等待:notEmpty.await();
// 唤醒:notEmpty.signal();
管道流( PipedInputStream / PipedOutputStream ):用于线程间字节流通信。
六、线程池详解
1. 核心参数( ThreadPoolExecutor 构造器)
corePoolSize :核心线程数(线程池长期保留的线程数,即使空闲)。
maximumPoolSize :最大线程数(核心线程+临时线程的最大数量)。
keepAliveTime :临时线程的空闲存活时间(超过该时间销毁)。
unit : keepAliveTime 的单位(如 TimeUnit.SECONDS )。
workQueue :任务队列(核心线程满时,新任务放入队列)。
threadFactory :线程创建工厂(自定义线程名称、优先级等)。
handler :拒绝策略(当队列满且线程数达最大值时,处理新任务的方式):
AbortPolicy (默认):抛出 RejectedExecutionException 。
CallerRunsPolicy :让提交任务的线程自己执行。
DiscardPolicy :直接丢弃新任务。
DiscardOldestPolicy :丢弃队列中最旧的任务,再提交新任务。
2. 工作原理
1. 新任务提交时,若核心线程未满,创建核心线程执行任务。
2. 核心线程满时,任务放入工作队列。
3. 队列满时,若未达最大线程数,创建临时线程执行任务。
4. 线程数达最大值且队列满时,触发拒绝策略。
3. 常用线程池
Executors.newFixedThreadPool(n) :固定大小线程池(核心线程=最大线程,无临时线程)。
Executors.newCachedThreadPool() :缓存线程池(核心线程=0,最大线程=Integer.MAX_VALUE,临时线程空闲60s销毁,适合短期任务)。
Executors.newSingleThreadExecutor() :单线程池(核心线程=最大线程=1,任务按顺序执行)。
Executors.newScheduledThreadPool(n) :定时任务线程池(支持延迟执行、周期性执行)。
七、并发工具类
CountDownLatch :倒计时器(让主线程等待多个子线程执行完毕后再继续)。
countDown() :计数器减1; await() :等待计数器为0。
CyclicBarrier :循环屏障(让多个线程到达屏障点后再一起继续执行,可重复使用)。
await() :线程到达屏障点后等待,所有线程到达后继续。
Semaphore :信号量(控制同时访问资源的线程数,如限流)。
acquire() :获取许可; release() :释放许可。
Exchanger :用于两个线程交换数据( exchange() 方法阻塞等待对方线程交换)。
八、线程的高级特性
线程组( ThreadGroup ):管理一组线程,可批量设置优先级、中断等(实际开发中较少使用,推荐线程池)。
线程优先级:范围1-10(默认5),优先级高的线程更可能被CPU调度(不保证绝对优先,依赖操作系统)。
守护线程(Daemon Thread):为其他线程服务(如GC线程),当所有非守护线程结束,守护线程自动终止。
通过 setDaemon(true) 设置(需在 start() 前调用)。
线程局部变量( ThreadLocal ):为每个线程提供独立的变量副本,避免线程安全问题(如 SimpleDateFormat 非线程安全,用 ThreadLocal 包装后每个线程单独持有一个实例)。
方法: set(T) 、 get() 、 remove() (使用后需 remove() ,避免内存泄漏)。
九、并发编程问题与解决方案
死锁:
原因:多个线程互相持有对方需要的锁,且不释放。
避免:按顺序获取锁、设置锁超时、使用 Lock 的 tryLock() 检测。
活锁:线程不断重试获取资源,导致任务无法推进(如两个线程互相释放锁后立即再次获取,陷入循环)。
解决:引入随机延迟重试。
饥饿:低优先级线程长期得不到CPU调度(如高优先级线程持续占用资源)。
解决:合理设置优先级、使用公平锁( ReentrantLock(true) )。
以上内容从基础到进阶,实际学习中需多写代码实践,理解线程同步、通信的场景和问题。