多线程
多线程是Java语言的重要特性之一,它允许程序并发执行多个任务。通过多线程技术,可以充分利用CPU资源,提高程序的执行效率,改善用户体验。本文将详细介绍Java多线程的相关概念和使用方法。
多线程基础
什么是线程?
线程是程序执行的最小单位,是进程中的一个独立执行路径。一个进程可以包含多个线程,它们共享进程的内存空间,但每个线程有自己的栈空间。
什么是多线程?
多线程是指在一个程序中同时运行多个线程的技术。多线程可以让程序的不同部分同时执行,从而提高程序的执行效率和响应速度。
多线程的优势
- 提高CPU利用率:当一个线程等待某个资源(如IO操作)时,其他线程可以继续执行
- 改善程序响应性:用户界面可以保持响应,而后台任务仍在执行
- 加速程序执行:对于可以并行处理的任务,多线程可以显著减少总执行时间
- 模块化设计:将不同功能拆分为不同线程,使程序结构更清晰
多线程的劣势
- 资源消耗:创建和维护线程需要额外的内存和CPU资源
- 复杂性增加:多线程程序更容易出现竞态条件、死锁等问题
- 调试困难:多线程程序的行为难以预测,调试比单线程程序更复杂
- 线程安全问题:需要额外的同步机制来保证数据一致性
Java中的线程模型
Java线程的实现方式
Java中的线程是基于操作系统的原生线程实现的,即每个Java线程都对应一个操作系统线程。这种实现方式被称为"1:1"线程模型。
Java线程的生命周期
Java线程的生命周期包括以下几个状态:
- 新建(New):线程被创建但还没有调用start()方法
- 就绪(Runnable):线程调用start()方法后,等待CPU调度
- 运行(Running):线程获得CPU资源,正在执行
- 阻塞(Blocked):线程因为某种原因放弃了CPU使用权,暂时停止运行
- 等待(Waiting):线程需要等待其他线程的通知才能继续运行
- 计时等待(Timed Waiting):线程在指定时间内等待
- 终止(Terminated):线程执行完毕或因异常终止
创建线程的方式
在Java中,有三种创建线程的方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
方式1:继承Thread类
通过继承Thread类并重写run()方法来创建线程。
public class ThreadExample extends Thread {
@Override
public void run() {
// 线程执行的代码
System.out.println("线程运行中 - 继承Thread类");
}
public static void main(String[] args) {
// 创建线程实例
ThreadExample thread = new ThreadExample();
// 启动线程
thread.start();
System.out.println("主线程运行中");
}
}方式2:实现Runnable接口
通过实现Runnable接口并实现run()方法来创建线程。这种方式更推荐,因为它避免了单继承的限制,并且更加灵活。
public class RunnableExample implements Runnable {
@Override
public void run() {
// 线程执行的代码
System.out.println("线程运行中 - 实现Runnable接口");
}
public static void main(String[] args) {
// 创建Runnable实例
RunnableExample runnable = new RunnableExample();
// 创建Thread对象并传入Runnable实例
Thread thread = new Thread(runnable);
// 启动线程
thread.start();
System.out.println("主线程运行中");
}
}方式3:实现Callable接口
Callable接口是Java 5引入的,它类似于Runnable,但可以返回结果并且可以抛出异常。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableExample implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 线程执行的代码,并返回结果
System.out.println("线程运行中 - 实现Callable接口");
return 42;
}
public static void main(String[] args) {
// 创建Callable实例
CallableExample callable = new CallableExample();
// 创建FutureTask对象并传入Callable实例
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 创建Thread对象并传入FutureTask实例
Thread thread = new Thread(futureTask);
// 启动线程
thread.start();
try {
// 获取线程执行结果
Integer result = futureTask.get();
System.out.println("线程返回结果: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("主线程运行中");
}
}线程控制方法
Thread类提供了多种方法来控制线程的执行。
启动线程
thread.start(); // 启动线程线程休眠
Thread.sleep(1000); // 线程休眠1000毫秒(1秒)线程等待
thread.join(); // 等待线程终止
thread.join(1000); // 等待线程终止,但最多等待1000毫秒线程让步
Thread.yield(); // 暂停当前正在执行的线程对象,并执行其他线程线程中断
thread.interrupt(); // 中断线程线程优先级
thread.setPriority(Thread.MAX_PRIORITY); // 设置线程优先级为最高
thread.setPriority(Thread.MIN_PRIORITY); // 设置线程优先级为最低
thread.setPriority(Thread.NORM_PRIORITY); // 设置线程优先级为普通线程状态查询
Thread.State state = thread.getState(); // 获取线程状态
boolean isAlive = thread.isAlive(); // 判断线程是否活跃
boolean isInterrupted = thread.isInterrupted(); // 判断线程是否被中断线程安全问题
什么是线程安全?
线程安全是指多线程环境下,程序能够正确地执行,不会出现数据不一致、竞态条件等问题。
线程安全问题的产生原因
- 共享资源:多个线程访问共享资源时,如果没有适当的同步机制,可能会导致数据不一致
- 原子性问题:某些操作虽然看起来是一个语句,但实际上可能由多个步骤组成
- 可见性问题:一个线程对共享变量的修改,其他线程可能无法立即看到
- 有序性问题:编译器、CPU等可能会对指令进行重排序,导致程序的执行顺序与预期不符
线程安全问题示例
public class ThreadSafetyExample {
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
// 创建10个线程,每个线程增加计数器1000次
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter++;
}
});
threads[i].start();
}
// 等待所有线程执行完毕
for (Thread thread : threads) {
thread.join();
}
// 理论上应该输出10000,但实际输出可能小于10000
System.out.println("计数器最终值: " + counter);
}
}线程同步机制
为了解决线程安全问题,Java提供了多种同步机制。
synchronized关键字
synchronized关键字是Java中最基本的同步机制,它可以保证在同一时间只有一个线程可以执行某个方法或代码块。
同步方法
public synchronized void synchronizedMethod() {
// 同步代码
}同步代码块
synchronized (lockObject) {
// 同步代码
}synchronized示例
public class SynchronizedExample {
private static int counter = 0;
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// 创建10个线程,每个线程增加计数器1000次
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
// 使用同步代码块
synchronized (lock) {
counter++;
}
}
});
threads[i].start();
}
// 等待所有线程执行完毕
for (Thread thread : threads) {
thread.join();
}
// 现在会正确输出10000
System.out.println("计数器最终值: " + counter);
}
}volatile关键字
volatile关键字可以确保变量的可见性,即当一个线程修改了一个volatile变量的值,新值会立即被其他线程看到。
public class VolatileExample {
private static volatile boolean running = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (running) {
// 线程一直运行
}
System.out.println("线程终止");
});
thread.start();
// 主线程休眠1秒
Thread.sleep(1000);
// 修改running变量
running = false;
System.out.println("设置running为false");
}
}ReentrantLock
ReentrantLock是Java 5引入的显式锁,它提供了比synchronized更灵活的锁定机制。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private static int counter = 0;
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 创建10个线程,每个线程增加计数器1000次
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
});
threads[i].start();
}
// 等待所有线程执行完毕
for (Thread thread : threads) {
thread.join();
}
System.out.println("计数器最终值: " + counter);
}
}线程安全的集合类
Java提供了多种线程安全的集合类,它们可以在多线程环境下安全地使用。
ConcurrentHashMap
线程安全的HashMap实现。
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 多线程环境下安全地使用
map.put("key1", 1);
map.put("key2", 2);
Integer value = map.get("key1");
System.out.println("Value: " + value);
}
}CopyOnWriteArrayList
线程安全的ArrayList实现,适用于读多写少的场景。
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 多线程环境下安全地使用
list.add("item1");
list.add("item2");
for (String item : list) {
System.out.println("Item: " + item);
}
}
}线程池
线程池是一种重用线程的机制,它可以预先创建一定数量的线程,当有任务需要执行时,从线程池中获取一个线程来执行任务,任务执行完毕后,线程不会被销毁,而是返回到线程池中等待下一个任务。
线程池的优势
- 重用线程:减少线程创建和销毁的开销
- 控制并发数量:避免创建过多线程导致的资源耗尽
- 提高响应速度:任务可以立即从线程池中获取线程执行,不需要等待线程创建
- 统一管理:可以对线程进行统一的管理、监控和调优
线程池的创建方式
Java提供了多种创建线程池的方式,最常用的是使用Executors工具类。
FixedThreadPool
创建固定大小的线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
// 创建固定大小为5的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 提交10个任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executorService.submit(() -> {
System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executorService.shutdown();
}
}CachedThreadPool
创建可缓存的线程池,线程池的大小会根据需要自动调整。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolExample {
public static void main(String[] args) {
// 创建可缓存的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 提交多个任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executorService.submit(() -> {
System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executorService.shutdown();
}
}SingleThreadExecutor
创建只有一个线程的线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorExample {
public static void main(String[] args) {
// 创建只有一个线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 提交多个任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executorService.submit(() -> {
System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executorService.shutdown();
}
}ScheduledThreadPool
创建可以调度任务的线程池。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExample {
public static void main(String[] args) {
// 创建可以调度任务的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
// 延迟2秒后执行任务
scheduledExecutorService.schedule(() -> {
System.out.println("延迟任务执行: " + Thread.currentThread().getName());
}, 2, TimeUnit.SECONDS);
// 延迟1秒后,每隔2秒执行一次任务
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("定期任务执行: " + Thread.currentThread().getName());
}, 1, 2, TimeUnit.SECONDS);
// 延迟1秒后,在上一次任务执行完后再隔2秒执行一次任务
scheduledExecutorService.scheduleWithFixedDelay(() -> {
System.out.println("延迟定期任务执行: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, 2, TimeUnit.SECONDS);
// 主线程休眠10秒后关闭线程池
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduledExecutorService.shutdown();
}
}ThreadPoolExecutor
ThreadPoolExecutor是线程池的核心实现类,它提供了更灵活的线程池配置选项。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorExample {
public static void main(String[] args) {
// 创建自定义的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, // 非核心线程的空闲超时时间
TimeUnit.SECONDS, // 超时时间单位
new ArrayBlockingQueue<>(100), // 任务队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交任务
for (int i = 0; i < 20; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
}
}线程通信
线程通信是多线程编程中的重要部分,它允许多个线程之间进行协作。
wait()、notify()和notifyAll()方法
这三个方法是Object类的方法,用于线程间的通信。
wait():使当前线程等待,直到其他线程调用此对象的notify()或notifyAll()方法notify():唤醒在此对象监视器上等待的单个线程notifyAll():唤醒在此对象监视器上等待的所有线程
public class ThreadCommunicationExample {
private static final Object lock = new Object();
private static boolean ready = false;
private static int sharedData = 0;
public static void main(String[] args) {
// 生产者线程
Thread producer = new Thread(() -> {
synchronized (lock) {
sharedData = 42;
ready = true;
System.out.println("生产者: 数据已准备好");
lock.notify(); // 唤醒消费者线程
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
synchronized (lock) {
while (!ready) {
try {
System.out.println("消费者: 等待数据准备");
lock.wait(); // 等待生产者通知
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者: 收到数据 - " + sharedData);
}
});
consumer.start();
producer.start();
}
}CountDownLatch
CountDownLatch是一个同步辅助类,它允许一个或多个线程等待直到在其他线程中执行的一组操作完成。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
// 创建一个CountDownLatch,计数为3
CountDownLatch latch = new CountDownLatch(3);
// 创建3个工作线程
for (int i = 0; i < 3; i++) {
final int workerId = i;
Thread worker = new Thread(() -> {
try {
System.out.println("工作线程 " + workerId + " 正在工作");
Thread.sleep(1000);
System.out.println("工作线程 " + workerId + " 完成工作");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 减少计数
}
});
worker.start();
}
System.out.println("主线程等待所有工作线程完成");
latch.await(); // 等待计数变为0
System.out.println("所有工作线程已完成,主线程继续执行");
}
}CyclicBarrier
CyclicBarrier是一个同步辅助类,它允许一组线程互相等待,直到到达一个公共屏障点。
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
// 创建一个CyclicBarrier,参与线程数为3,完成后执行一个任务
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程都已到达屏障点,执行屏障任务");
});
// 创建3个线程
for (int i = 0; i < 3; i++) {
final int threadId = i;
Thread thread = new Thread(() -> {
try {
System.out.println("线程 " + threadId + " 正在运行");
Thread.sleep((long) (Math.random() * 2000));
System.out.println("线程 " + threadId + " 到达屏障点,等待其他线程");
barrier.await(); // 等待其他线程到达屏障点
System.out.println("线程 " + threadId + " 继续执行");
} catch (Exception e) {
e.printStackTrace();
}
});
thread.start();
}
}
}Semaphore
Semaphore是一个计数信号量,它控制对资源的访问,可以限制同时访问某个资源的线程数。
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
// 创建一个信号量,最多允许3个线程同时访问资源
Semaphore semaphore = new Semaphore(3);
// 创建10个线程尝试访问资源
for (int i = 0; i < 10; i++) {
final int threadId = i;
Thread thread = new Thread(() -> {
try {
System.out.println("线程 " + threadId + " 尝试获取资源");
semaphore.acquire(); // 获取许可
System.out.println("线程 " + threadId + " 获取到资源,正在使用");
Thread.sleep(1000); // 模拟使用资源
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("线程 " + threadId + " 释放资源");
semaphore.release(); // 释放许可
}
});
thread.start();
}
}
}线程安全的设计模式
单例模式的线程安全实现
饿汉式
public class SingletonEager {
// 类加载时就创建实例
private static final SingletonEager instance = new SingletonEager();
// 私有构造方法
private SingletonEager() {}
// 提供全局访问点
public static SingletonEager getInstance() {
return instance;
}
}懒汉式(双重检查锁定)
public class SingletonLazy {
// volatile防止指令重排序
private static volatile SingletonLazy instance;
// 私有构造方法
private SingletonLazy() {}
// 提供全局访问点
public static SingletonLazy getInstance() {
if (instance == null) { // 第一次检查
synchronized (SingletonLazy.class) {
if (instance == null) { // 第二次检查
instance = new SingletonLazy();
}
}
}
return instance;
}
}静态内部类
public class SingletonInnerClass {
// 私有构造方法
private SingletonInnerClass() {}
// 静态内部类
private static class SingletonHolder {
private static final SingletonInnerClass instance = new SingletonInnerClass();
}
// 提供全局访问点
public static SingletonInnerClass getInstance() {
return SingletonHolder.instance;
}
}枚举类型
public enum SingletonEnum {
INSTANCE;
// 可以添加其他方法
public void doSomething() {
System.out.println("SingletonEnum is doing something");
}
}多线程常见问题
死锁
死锁是指两个或多个线程互相等待对方持有的资源,导致所有线程都无法继续执行的状态。
死锁示例:
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
// 线程1先获取lock1,再尝试获取lock2
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("线程1获取到lock1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1尝试获取lock2");
synchronized (lock2) {
System.out.println("线程1获取到lock2");
}
}
});
// 线程2先获取lock2,再尝试获取lock1
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("线程2获取到lock2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2尝试获取lock1");
synchronized (lock1) {
System.out.println("线程2获取到lock1");
}
}
});
thread1.start();
thread2.start();
}
}避免死锁的方法:
- 按顺序获取锁
- 避免长时间持有锁
- 使用锁超时机制
- 使用
Lock接口的tryLock()方法
活锁
活锁是指线程不断地尝试获取资源,但因为某种条件未满足而不断重试,导致线程看起来是在执行,但实际上没有进展。
活锁示例:
public class LivelockExample {
private boolean isAvailable = false;
public synchronized void tryToGetResource() {
while (isAvailable) {
System.out.println(Thread.currentThread().getName() + ": 资源不可用,等待重试");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 重试获取资源
}
isAvailable = true;
System.out.println(Thread.currentThread().getName() + ": 获取到资源");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
isAvailable = false;
System.out.println(Thread.currentThread().getName() + ": 释放资源");
}
public static void main(String[] args) {
LivelockExample example = new LivelockExample();
Thread thread1 = new Thread(example::tryToGetResource, "线程1");
Thread thread2 = new Thread(example::tryToGetResource, "线程2");
thread1.start();
thread2.start();
}
}避免活锁的方法:
- 引入随机延迟
- 使用不同的重试策略
- 设置最大重试次数
线程饥饿
线程饥饿是指某个线程因为优先级低或其他原因,长时间无法获得CPU资源或其他需要的资源,导致线程无法正常执行。
避免线程饥饿的方法:
- 避免设置过高的线程优先级
- 使用公平锁
- 避免线程持有锁的时间过长
- 使用线程池管理线程
多线程最佳实践
- 优先使用实现Runnable接口的方式创建线程,而不是继承Thread类,以避免单继承的限制
- 使用线程池管理线程,避免频繁创建和销毁线程
- 使用并发集合类,如ConcurrentHashMap、CopyOnWriteArrayList等,而不是同步集合类
- 避免使用synchronized的String常量锁,因为它们是全局共享的
- 尽量减小同步代码块的范围,只同步必要的代码
- 使用Lock接口替代synchronized,以获得更灵活的锁定机制
- 使用volatile保证变量的可见性,避免不必要的同步
- 避免死锁,通过按顺序获取锁、避免长时间持有锁等方式
- 正确关闭线程池,使用shutdown()或shutdownNow()方法
- 使用ThreadLocal存储线程局部变量,避免共享变量带来的线程安全问题
小结
- 多线程是Java语言的重要特性,它允许程序并发执行多个任务
- Java中创建线程的方式有三种:继承Thread类、实现Runnable接口和实现Callable接口
- 线程有多种状态,包括新建、就绪、运行、阻塞、等待、计时等待和终止
- 线程安全问题是多线程编程中最常见的问题,可以通过synchronized、volatile、ReentrantLock等机制解决
- 线程池可以重用线程,提高程序性能,Java提供了多种线程池实现
- 线程间可以通过wait()、notify()、CountDownLatch、CyclicBarrier、Semaphore等方式进行通信
- 多线程编程中需要注意避免死锁、活锁、线程饥饿等问题
- 多线程编程是Java开发中的重要技能,需要深入理解并正确使用
掌握Java多线程编程对于开发高性能、并发的Java应用程序至关重要。通过合理地使用多线程,可以充分利用系统资源,提高程序的执行效率和响应速度。