Java多线程编程详解
Java多线程编程详解
什么是线程?
线程是程序执行的最小单位,一个进程可以包含多个线程。多线程编程可以充分利用CPU资源,提高程序的执行效率。
线程与进程的区别
- 进程:是操作系统分配资源的最小单位,每个进程都有自己的内存空间
- 线程:是程序执行的最小单位,共享进程的内存空间
- 区别:
- 进程之间相互独立,线程之间共享内存
- 创建进程的开销比创建线程大
- 进程切换的开销比线程切换大
- 一个进程崩溃不会影响其他进程,一个线程崩溃可能会影响整个进程
Java中创建线程的方式
1. 继承Thread类
public class MyThread extends Thread { @Override public void run() { // 线程执行的代码 for (int i = 0; i < 10; i++) { System.out.println("MyThread: " + i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }}
// 使用public class ThreadDemo { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // 启动线程 }}2. 实现Runnable接口
public class MyRunnable implements Runnable { @Override public void run() { // 线程执行的代码 for (int i = 0; i < 10; i++) { System.out.println("MyRunnable: " + i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }}
// 使用public class RunnableDemo { public static void main(String[] args) { MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); // 启动线程 }}3. 实现Callable接口
import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;
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; }}
// 使用public class CallableDemo { public static void main(String[] args) { MyCallable callable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<>(callable); Thread thread = new Thread(futureTask); thread.start(); // 启动线程
try { // 获取线程执行结果 Integer result = futureTask.get(); System.out.println("Sum: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }}4. 使用线程池
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class ThreadPoolDemo { public static void main(String[] args) { // 创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(5);
// 提交任务 for (int i = 0; i < 10; i++) { final int taskId = i; executorService.submit(new Runnable() { @Override public void run() { System.out.println("Task " + taskId + " is running"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }
// 关闭线程池 executorService.shutdown(); }}线程的生命周期
Java线程的生命周期包括以下状态:
- 新建(New):线程对象被创建,但尚未启动
- 就绪(Runnable):线程已经启动,等待CPU分配时间片
- 运行(Running):线程正在执行
- 阻塞(Blocked):线程等待锁或其他资源
- 等待(Waiting):线程等待其他线程的通知
- 超时等待(Timed Waiting):线程等待指定时间后自动唤醒
- 终止(Terminated):线程执行完毕或异常终止
线程的常用方法
1. 线程控制方法
start():启动线程run():线程执行的方法sleep(long millis):使线程睡眠指定时间join():等待线程执行完毕interrupt():中断线程isInterrupted():检查线程是否被中断interrupted():检查线程是否被中断,并清除中断状态yield():线程礼让,让出CPU时间片setDaemon(boolean on):设置线程为守护线程isDaemon():检查线程是否为守护线程setPriority(int newPriority):设置线程优先级getPriority():获取线程优先级
2. 线程状态方法
getState():获取线程状态getName():获取线程名称setName(String name):设置线程名称getId():获取线程IDisAlive():检查线程是否存活
线程安全
什么是线程安全?
线程安全是指多个线程同时访问共享资源时,不会导致数据不一致或其他意外情况。
线程安全问题的原因
- 共享资源:多个线程同时访问同一个资源
- 非原子操作:操作不是原子的,可能被其他线程中断
- 重排序:编译器或CPU对指令进行重排序
- 可见性:一个线程对共享变量的修改,其他线程可能看不到
解决线程安全问题的方法
1. 同步代码块
synchronized (lockObject) { // 线程安全的代码}2. 同步方法
public synchronized void method() { // 线程安全的代码}3. Lock接口
import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;
Lock lock = new ReentrantLock();try { lock.lock(); // 线程安全的代码} finally { lock.unlock();}4. 原子类
import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger count = new AtomicInteger(0);count.incrementAndGet(); // 原子操作5. 线程安全的集合
import java.util.concurrent.CopyOnWriteArrayList;import java.util.concurrent.ConcurrentHashMap;
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();6. 线程局部变量
import java.util.concurrent.ThreadLocalRandom;
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);threadLocal.set(100);int value = threadLocal.get();线程通信
1. wait()、notify()和notifyAll()方法
public class ThreadCommunication { private static final Object lock = new Object(); private static boolean flag = false;
public static void main(String[] args) { // 生产者线程 new Thread(() -> { synchronized (lock) { while (!flag) { try { System.out.println("生产者等待..."); lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("生产者生产数据"); flag = false; lock.notify(); } }).start();
// 消费者线程 new Thread(() -> { synchronized (lock) { System.out.println("消费者消费数据"); flag = true; lock.notify(); try { System.out.println("消费者等待..."); lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }}2. CountDownLatch
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) { final int taskId = i; new Thread(() -> { System.out.println("Task " + taskId + " is running"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Task " + taskId + " is done"); latch.countDown(); }).start(); }
System.out.println("Waiting for all tasks to complete..."); latch.await(); System.out.println("All tasks are completed"); }}3. CyclicBarrier
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All threads are ready"); });
for (int i = 0; i < 3; i++) { final int threadId = i; new Thread(() -> { System.out.println("Thread " + threadId + " is ready"); try { barrier.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("Thread " + threadId + " continues"); }).start(); } }}4. Semaphore
import java.util.concurrent.Semaphore;
public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore = new Semaphore(2); // 最多允许2个线程同时执行
for (int i = 0; i < 5; i++) { final int threadId = i; new Thread(() -> { try { semaphore.acquire(); System.out.println("Thread " + threadId + " is running"); Thread.sleep(1000); System.out.println("Thread " + threadId + " is done"); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } }).start(); } }}线程池
什么是线程池?
线程池是管理线程的容器,它可以重用线程,减少线程创建和销毁的开销。
线程池的优点
- 重用线程:减少线程创建和销毁的开销
- 控制并发数:避免过多线程导致的资源耗尽
- 管理线程:提供线程的统一管理和监控
- 提高响应速度:线程已经创建,可立即执行任务
线程池的创建方式
1. Executors工厂方法
newFixedThreadPool(int nThreads):创建固定大小的线程池newCachedThreadPool():创建可缓存的线程池newSingleThreadExecutor():创建单线程的线程池newScheduledThreadPool(int corePoolSize):创建定时任务的线程池
2. ThreadPoolExecutor构造方法
import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.RejectedExecutionHandler;
ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, // 核心线程数 maximumPoolSize, // 最大线程数 keepAliveTime, // 线程存活时间 TimeUnit.SECONDS, // 时间单位 new LinkedBlockingQueue<>(), // 任务队列 new ThreadPoolExecutor.AbortPolicy() // 拒绝策略);线程池的工作原理
- 当提交任务时,如果核心线程数未满,创建新线程执行任务
- 如果核心线程数已满,将任务加入任务队列
- 如果任务队列已满,且线程数未达到最大线程数,创建新线程执行任务
- 如果线程数已达到最大线程数,执行拒绝策略
线程池的拒绝策略
AbortPolicy:抛出RejectedExecutionException异常CallerRunsPolicy:由调用者线程执行任务DiscardPolicy:丢弃任务DiscardOldestPolicy:丢弃队列中最旧的任务
并发集合
1. ConcurrentHashMap
线程安全的HashMap,支持高并发读写。
import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();map.put("key", 1);Integer value = map.get("key");2. CopyOnWriteArrayList
线程安全的ArrayList,适用于读多写少的场景。
import java.util.concurrent.CopyOnWriteArrayList;
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();list.add("element");String element = list.get(0);3. ConcurrentLinkedQueue
线程安全的无界队列,适用于高并发场景。
import java.util.concurrent.ConcurrentLinkedQueue;
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();queue.offer("element");String element = queue.poll();4. BlockingQueue
阻塞队列,支持线程安全的入队和出队操作。
ArrayBlockingQueue:基于数组的有界阻塞队列LinkedBlockingQueue:基于链表的可选有界阻塞队列PriorityBlockingQueue:基于优先级的无界阻塞队列DelayQueue:基于延迟时间的无界阻塞队列SynchronousQueue:同步队列,不存储元素
并发工具类
1. Atomic类
AtomicInteger:原子整数AtomicLong:原子长整数AtomicBoolean:原子布尔值AtomicReference:原子引用
import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger count = new AtomicInteger(0);count.incrementAndGet(); // 原子递增count.decrementAndGet(); // 原子递减count.addAndGet(10); // 原子增加指定值count.compareAndSet(0, 1); // 原子比较并设置2. ThreadLocal
线程局部变量,每个线程都有自己的副本。
import java.util.concurrent.ThreadLocalRandom;
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);threadLocal.set(100);int value = threadLocal.get();threadLocal.remove(); // 清理资源,避免内存泄漏3. CountDownLatch
倒计时门闩,等待多个线程完成。
4. CyclicBarrier
循环屏障,等待多个线程到达屏障点。
5. Semaphore
信号量,控制同时访问资源的线程数。
6. Exchanger
交换器,两个线程交换数据。
import java.util.concurrent.Exchanger;
Exchanger<String> exchanger = new Exchanger<>();
new Thread(() -> { try { String data1 = "Data from thread 1"; System.out.println("Thread 1 sending: " + data1); String data2 = exchanger.exchange(data1); System.out.println("Thread 1 received: " + data2); } catch (InterruptedException e) { e.printStackTrace(); }}).start();
new Thread(() -> { try { String data2 = "Data from thread 2"; System.out.println("Thread 2 sending: " + data2); String data1 = exchanger.exchange(data2); System.out.println("Thread 2 received: " + data1); } catch (InterruptedException e) { e.printStackTrace(); }}).start();最佳实践
-
优先使用Runnable接口:相比于继承Thread类,实现Runnable接口更灵活,避免了单继承的限制。
-
使用线程池:优先使用线程池管理线程,而不是直接创建线程。
-
避免使用ThreadLocal的内存泄漏:使用完ThreadLocal后,调用remove()方法清理资源。
-
合理使用同步:只同步必要的代码,避免过度同步影响性能。
-
使用volatile关键字:对于需要可见性的变量,使用volatile关键字。
-
使用原子类:对于简单的计数器等操作,使用原子类而不是同步。
-
避免死锁:
- 按顺序获取锁
- 使用tryLock()尝试获取锁
- 使用定时锁
-
使用并发集合:对于多线程场景,使用线程安全的并发集合。
-
设置合理的线程池参数:根据实际需求设置线程池的核心线程数、最大线程数等参数。
-
监控线程状态:使用线程池的监控功能,及时发现线程问题。
常见陷阱
-
线程启动方式错误:调用run()方法而不是start()方法,导致线程在主线程中执行。
-
死锁:多个线程相互等待对方释放锁,导致程序卡死。
-
活锁:线程不断改变自己的状态,导致无法继续执行。
-
饥饿:某些线程一直无法获取CPU时间片,导致无法执行。
-
内存泄漏:ThreadLocal使用后未清理,导致内存泄漏。
-
线程池耗尽:提交过多任务到线程池,导致线程池耗尽。
-
过度同步:同步代码块过大,导致性能下降。
-
原子性问题:非原子操作导致的数据不一致。
-
可见性问题:一个线程对共享变量的修改,其他线程看不到。
-
重排序问题:编译器或CPU对指令进行重排序,导致意外结果。
总结
多线程编程是Java编程中的重要部分,它可以充分利用CPU资源,提高程序的执行效率。但多线程编程也带来了线程安全、线程通信等问题,需要我们谨慎处理。
本文介绍了Java多线程编程的基本概念、创建线程的方式、线程的生命周期、线程安全、线程通信、线程池等内容。希望本文能够帮助你更好地理解和使用Java的多线程编程。
练习
-
编写一个程序,使用继承Thread类的方式创建线程。
-
编写一个程序,使用实现Runnable接口的方式创建线程。
-
编写一个程序,使用实现Callable接口的方式创建线程,并获取线程执行结果。
-
编写一个程序,使用线程池执行多个任务。
-
编写一个程序,演示线程的sleep()、join()、yield()方法。
-
编写一个程序,演示线程安全问题,并使用同步代码块解决。
-
编写一个程序,使用wait()、notify()方法实现线程通信。
-
编写一个程序,使用CountDownLatch等待多个线程完成。
-
编写一个程序,使用CyclicBarrier实现多个线程的同步。
-
编写一个程序,使用Semaphore控制同时访问资源的线程数。
通过这些练习,你将更加熟悉Java的多线程编程,为后续的学习做好准备。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!