是关于Java多线程使用的详细介绍:
创建线程的方式
-
继承Thread类
- 特点:直接继承
Thread
类并重写run()
方法,这种方式简单直接,但由于Java的单继承特性(每个类只能有一个父类),会占用唯一的继承机会,导致无法再继承其他父类,线程与任务强耦合,不利于代码复用,因此不推荐作为主流方案使用。class MyThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName() + ": MyThread is running"); } } // 启动线程 MyThread myThread = new MyThread(); myThread.start();
- 适用场景:适合简单演示或临时需求,但在实际项目中较少采用。
- 特点:直接继承
-
实现Runnable接口
- 特点:通过实现
Runnable
接口并重写run()
方法,将任务逻辑与线程分离,这种方式避免了单继承的限制,支持Lambda表达式简化代码,并且可以与线程池配合使用,是更推荐的做法。class MyRunnable implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + ": MyRunnable.run"); } } // 启动线程 new Thread(new MyRunnable()).start(); // 或使用Lambda简化 new Thread(() -> System.out.println("Lambda方式运行")).start();
- 优势:任务对象可复用,多个线程能共享同一个任务实例;便于管理和维护。
- 特点:通过实现
-
实现Callable接口 + FutureTask
- 特点:若需要获取线程执行后的返回值或处理异常,可采用此方案,需实现
Callable<V>
泛型接口的call()
方法,并通过FutureTask
包装后提交给线程执行,常用于异步计算结果的场景,示例如下:class MyCallable implements Callable<String> { @Override public String call() throws Exception { return Thread.currentThread().getName() + ": myCallable result"; } } // 包装为FutureTask并启动 MyCallable myCallable = new MyCallable(); FutureTask<String> futureTask = new FutureTask<>(myCallable); new Thread(futureTask).start(); // 获取结果(阻塞式) System.out.println(futureTask.get());
- 关键点:
FutureTask
提供了对计算结果的访问能力,支持超时控制和取消操作。
- 特点:若需要获取线程执行后的返回值或处理异常,可采用此方案,需实现
线程池的使用(企业级推荐)
线程池由ExecutorService
接口及其实现类管理,可有效减少频繁创建/销毁线程的资源开销,并提供多种预定义类型:
| 类型 | 说明 | 示例创建方式 |
|———————|———————————————————————-|———————————-|
| FixedThreadPool
| 固定数量的核心线程,适用于长期稳定的任务负载 | Executors.newFixedThreadPool(n)
|
| CachedThreadPool
| 根据需求动态调整线程数,空闲线程会自动回收 | Executors.newCachedThreadPool()
|
| ScheduledThreadPool
| 支持定时或周期性执行任务 | Executors.newScheduledThreadPool(n)
|
| SingleThreadExecutor
| 单线程顺序执行任务,保证先进先出 | Executors.newSingleThreadExecutor()
|
任务提交方式:
execute(Runnable task)
:无返回值,适合纯执行型任务。submit(Callable task)
:返回Future
对象,可用于获取结果或异常处理。
示例代码:
ExecutorService pool = Executors.newFixedThreadPool(5); pool.execute(() -> System.out.println("执行无返回值任务")); Future<Integer> future = pool.submit(() -> 42); System.out.println("计算结果:" + future.get()); // 阻塞直到结果可用 pool.shutdown(); // 关闭线程池前应确保所有任务完成
线程控制与协作机制
-
基础操作
- 启动:调用
start()
使线程进入就绪状态(非直接调用run()
)。 - 礼让:
Thread.yield()
提示调度器让出CPU资源,但非强制。 - 等待终止:
join()
方法让当前线程等待目标线程结束。Thread t1 = new Thread(() -> { Thread.sleep(2000); }); t1.start(); t1.join(); // 确保t1完成后再继续执行主线程
- 中断:调用
interrupt()
设置中断标志,线程需主动检查isInterrupted()
并响应,注意:不能直接停止正在运行的线程(如已弃用的stop()
方法)。 - 休眠:
Thread.sleep(millis)
使当前线程暂停指定时间。
- 启动:调用
-
同步机制解决线程安全问题
- 问题根源:多线程并发修改共享数据时可能导致数据不一致(如超卖、重复消费),例如未加锁的售票系统会出现错误计数。
- 解决方案:
- synchronized关键字:修饰方法或代码块,确保同一时刻只有一个线程访问临界区。
private int ticket = 1; public void run() { synchronized (this) { // “this”作为锁对象 if (ticket > 100) break; System.out.println(Thread.currentThread().getName() + ": " + ticket++); } }
- 显式锁ReentrantLock:比
synchronized
更灵活,支持尝试获取锁、超时等待等功能。Lock lock = new ReentrantLock(); public void run() { lock.lock(); try { / 操作共享资源 / } finally { lock.unlock(); } }
- synchronized关键字:修饰方法或代码块,确保同一时刻只有一个线程访问临界区。
- volatile变量:保证内存可见性,适用于标志位等轻量级通信场景。
- ThreadLocal:为每个线程提供独立的变量副本,避免跨线程干扰。
高级工具类与设计模式
- CountDownLatch:倒计时器,允许主线程等待多个子线程完成,典型用法是并行任务完成后汇归纳果。
- CyclicBarrier:循环屏障,让一组线程相互等待至达标后同时继续执行,适合分批处理场景。
- Semaphore:信号量机制,控制同时访问资源的线程数量(如数据库连接池)。
- 生产者-消费者模型:结合
BlockingQueue
实现异步缓冲,解耦生产速度与消费速度差异。
FAQs
-
Q: 为什么推荐使用实现Runnable接口而不是继承Thread类?
A: 因为Java不支持多继承,若已继承其他父类则无法再继承Thread;实现Runnable可将任务逻辑与线程生命周期分离,提高代码复用性和灵活性,同一个Runnable实例可被多个线程共享执行。 -
Q: 如何优雅地停止一个正在运行的线程?
A: 应使用中断机制(调用interrupt()
方法),并在线程内部定期检查中断状态(如while (!Thread.currentThread().isInterrupted())
),避免直接调用已弃用的stop()
方法,以防止资源泄露或数据损坏。Thread worker = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { // 执行任务逻辑 } }); worker.start(); // 需要停止时调用 worker.interrupt();
通过合理选择创建方式、利用线程池管理资源、结合同步机制保证安全,并掌握协作工具类,可以高效地开发Java多
原创文章,发布者:酷盾叔,转转请注明出处:https://www.kd.cn/ask/92128.html