多线程编程
多线程编程
多线程编程是Java中的一个重要特性,允许程序同时执行多个任务,提高程序的性能和响应速度。
多线程编程概述
什么是线程
线程是程序执行的最小单位,一个进程可以包含多个线程。线程共享进程的内存空间,但有自己的栈空间。
多线程的优势
- 提高程序的性能和响应速度。
- 充分利用多核处理器的资源。
- 简化复杂任务的处理,如异步操作、并行计算等。
- 提高程序的吞吐量。
多线程的应用场景
- 服务器端编程,如Web服务器、数据库服务器等。
- 图形用户界面(GUI)应用程序,保持界面响应。
- 并行计算,如科学计算、数据分析等。
- 异步操作,如文件IO、网络IO等。
线程的创建与启动
继承Thread类
通过继承Thread
类并重写run()
方法创建线程。
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running: " + Thread.currentThread().getName());
}
}
// 启动线程
MyThread thread = new MyThread();
thread.start(); // 调用start()方法启动线程,而不是run()方法
实现Runnable接口
通过实现Runnable
接口创建线程。
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running: " + Thread.currentThread().getName());
}
}
// 启动线程
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
实现Callable接口
通过实现Callable
接口创建线程,可以返回结果。
import java.util.concurrent.*;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
// 启动线程
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(new MyCallable());
try {
Integer result = future.get(); // 获取结果,会阻塞直到任务完成
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown(); // 关闭线程池
线程的启动
线程通过调用start()
方法启动,而不是直接调用run()
方法。start()
方法会创建一个新的线程,并在新线程中执行run()
方法。
线程的生命周期
线程的生命周期包括以下几个状态:
新建状态(New)
当创建线程对象时,线程处于新建状态。
就绪状态(Runnable)
当调用start()
方法后,线程进入就绪状态,等待CPU调度。
运行状态(Running)
当CPU调度线程时,线程进入运行状态,执行run()
方法。
阻塞状态(Blocked)
线程可能因为以下原因进入阻塞状态:
- 调用
sleep()
方法 - 调用
wait()
方法 - 等待IO操作完成
- 等待获取锁
死亡状态(Dead)
当run()
方法执行完毕或线程被中断时,线程进入死亡状态。
线程的控制
线程的休眠(sleep)
sleep()
方法使线程暂停执行指定的时间。
try {
Thread.sleep(1000); // 休眠1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
线程的等待(wait)和通知(notify/notifyAll)
wait()
方法使线程等待,直到其他线程调用notify()
或notifyAll()
方法唤醒它。
// 等待
synchronized (object) {
try {
object.wait(); // 线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 通知
synchronized (object) {
object.notify(); // 唤醒一个等待的线程
// object.notifyAll(); // 唤醒所有等待的线程
}
线程的中断(interrupt)
interrupt()
方法用于中断线程。
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
}
System.out.println("Thread is interrupted");
});
thread.start();
// 中断线程
thread.interrupt();
线程的优先级
线程的优先级范围是1-10,默认优先级是5。优先级高的线程更容易被CPU调度。
Thread thread = new Thread();
thread.setPriority(Thread.MAX_PRIORITY); // 设置为最高优先级(10)
thread.setPriority(Thread.MIN_PRIORITY); // 设置为最低优先级(1)
thread.setPriority(Thread.NORM_PRIORITY); // 设置为默认优先级(5)
线程同步
线程同步用于解决多线程并发访问共享资源时产生的问题,如数据不一致、死锁等。
同步代码块
使用synchronized
关键字创建同步代码块。
synchronized (object) {
// 访问共享资源的代码
}
同步方法
使用synchronized
关键字修饰方法。
public synchronized void method() {
// 访问共享资源的代码
}
锁机制(ReentrantLock)
ReentrantLock
是Java 5引入的锁机制,提供了比synchronized
更灵活的同步控制。
import java.util.concurrent.locks.ReentrantLock;
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 访问共享资源的代码
} finally {
lock.unlock(); // 释放锁
}
死锁及其解决方法
死锁是指两个或多个线程互相等待对方释放资源,导致程序无法继续执行。
避免死锁的方法:
- 按顺序获取锁
- 使用
tryLock()
方法尝试获取锁 - 减少锁的持有时间
- 使用锁超时机制
线程池
线程池是一种管理线程的机制,可以重用线程,避免频繁创建和销毁线程的开销。
线程池的优势
- 重用线程,减少线程创建和销毁的开销。
- 控制线程数量,避免资源耗尽。
- 提供任务排队和管理机制。
- 支持定时任务和周期性任务。
线程池的创建
Java提供了Executors
类来创建线程池:
// 创建固定大小的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// 创建单线程线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 创建缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 创建定时线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
线程池的参数
ThreadPoolExecutor
类提供了更灵活的线程池配置,其构造方法如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// ...
}
参数说明:
corePoolSize
:核心线程数maximumPoolSize
:最大线程数keepAliveTime
:非核心线程的空闲超时时间unit
:超时时间的单位workQueue
:任务队列threadFactory
:线程工厂handler
:拒绝策略
线程池的使用
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " is running by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown(); // 关闭线程池
并发集合
Java提供了一些线程安全的并发集合:
ConcurrentHashMap
ConcurrentHashMap
是线程安全的HashMap
实现,支持并发读写。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key1", 1);
map.put("key2", 2);
int value = map.get("key1");
CopyOnWriteArrayList
CopyOnWriteArrayList
是线程安全的ArrayList
实现,适合读多写少的场景。
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("element1");
list.add("element2");
String element = list.get(0);
BlockingQueue
BlockingQueue
是阻塞队列,支持线程安全的入队和出队操作。
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("element1"); // 如果队列满,则阻塞
String element = queue.take(); // 如果队列空,则阻塞
并发集合
什么是线程安全
线程安全是指多线程环境下,程序的行为是预期的,不会出现数据不一致、死锁等问题。
线程安全的实现方法
- 使用同步代码块或同步方法
- 使用锁机制
- 使用线程安全的集合
- 使用原子变量
- 避免共享可变状态
线程安全的常见问题
- 竞态条件(Race Condition)
- 死锁(Deadlock)
- 活锁(Livelock)
- 饥饿(Starvation)
多线程编程的最佳实践
避免不必要的同步
只在需要访问共享资源时进行同步,减少同步的范围。
正确使用线程池
根据任务的性质和数量选择合适的线程池,避免创建过多的线程。
避免死锁
按顺序获取锁,使用tryLock()
方法,减少锁的持有时间。
合理设置线程优先级
不要过度依赖线程优先级,优先级高的线程不一定总是先执行。
使用并发工具类
使用java.util.concurrent
包提供的并发工具类,如CountDownLatch
、CyclicBarrier
、Semaphore
等。
测试多线程代码
多线程代码的测试比较困难,需要考虑各种并发场景,确保线程安全。