多线程与并发(Concurrency)是Java编程中的重要主题,它们允许程序在同一时间处理多个任务,从而提高应用程序的效率和响应速度。在Java中,实现多线程的方式主要包括通过Thread
类、Runnable
接口创建线程,以及使用更高级别的抽象如ExecutorService
框架来管理和调度线程池。此外,Java还提供了诸如Lock
、Condition
和Atomic
类等工具来帮助开发者更好地控制并发行为,确保线程安全。
创建线程
- 继承
Thread
类:这是最直接的方法之一,但因为Java不支持多重继承,所以如果一个类已经继承了另一个类,则不能再继承Thread
。因此,这种方法较少被推荐。 - 实现
Runnable
接口:相比于继承Thread
类,这种方式更为灵活,因为它不仅可以让一个类实现多个接口,还可以避免单继承带来的局限性。Runnable
接口只有一个方法需要实现——run()
,该方法包含了线程要执行的任务逻辑。 - 使用
Callable
和Future
:对于那些需要返回结果或者可能抛出异常的任务来说,Callable
接口是一个更好的选择。它类似于Runnable
,但是它的call()
方法可以返回一个值并且声明异常。为了获取Callable
的结果,通常会将其提交给ExecutorService
,并接收一个Future
对象作为返回值,通过调用Future.get()
可以阻塞直到任务完成并获得结果。
管理线程池
ExecutorService
:这是一个更高层次的API,用于简化线程管理。它允许你创建不同类型的线程池,例如固定大小的线程池、缓存线程池或定时任务线程池。ExecutorService
不仅能够减少手动创建和销毁线程的工作量,而且还能优化资源利用,比如重用已有的线程而不是每次都创建新的线程。常见的工厂方法有newCachedThreadPool()
、newFixedThreadPool(int nThreads)
、newScheduledThreadPool(int corePoolSize)
等,分别对应于可扩展线程池、定长线程池和支持定时及周期性任务执行的线程池。
同步机制
synchronized
关键字:这是最基础也是最常用的同步手段之一,它可以用来修饰方法或代码块,确保同一时刻只有一个线程能访问被同步的区域。然而,synchronized
存在一些局限性,例如它只能基于对象锁,并且不能提供更加细粒度的锁定策略。Lock
接口及其子类:为了解决Synchronized
的一些不足之处,Java引入了Lock
接口及其具体实现,如ReentrantLock
。与Synchronized
相比,Lock
提供了更多的灵活性,包括尝试获取锁(tryLock
)、轮询获取锁(lockInterruptibly
)、公平锁设置等功能。更重要的是,Lock
结合Condition
对象可以实现比wait/notify
更为强大的等待通知机制。
并发工具
Condition
:条件变量用于协调线程之间的通信,特别是在生产者-消费者模式下非常有用。每个Condition
都关联着一个Lock
实例,允许线程在满足特定条件时等待或唤醒其他线程。这比传统的Object.wait()/notify()
提供了更高的可控性和安全性。Atomic
类:这些类位于java.util.concurrent.atomic
包中,提供了原子操作的支持,即对共享变量的操作不会受到其他线程干扰。例如,AtomicInteger
允许以原子方式更新整数值,而不需要额外的锁保护。这类工具非常适合用于计数器或者其他需要频繁更新的状态信息。