并发编程:Java中的多线程与线程池!
全文目录:
- 开篇语
- 线程的基础概念
- 线程生命周期
- 线程调度
- 线程安全
- 线程池:Executor框架、线程池的管理与调优
- Executor框架
- 线程池的管理与调优
- 并发工具类:ReentrantLock、CountDownLatch、CyclicBarrier等
- ReentrantLock
- CountDownLatch
- CyclicBarrier
- 小结
- 文末
开篇语
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
线程的基础概念
当我们谈到并发编程时,最核心的概念之一便是线程。线程是一条执行路径,在Java中,线程允许你将一个程序分成多个并行执行的任务。Java中多线程的核心作用是提高程序的执行效率,尤其在处理I/O操作、并行计算等场景中具有重要意义。然而,线程的使用并不是那么简单,特别是在资源共享和同步时,可能会导致诸如死锁、竞态条件等问题。因此,理解线程的生命周期、调度以及线程安全机制,是编写高效并发程序的基础。
线程生命周期
在Java中,线程从创建到终止的过程被称为线程生命周期。线程的生命周期包括几个重要的状态,每个状态有其特定的行为和转变过程。下面简要介绍每个线程状态及其转变:
-
新建状态(New):当你创建一个线程对象时,线程进入“新建”状态。此时,线程还没有开始执行。
Thread t = new Thread(); // 线程t处于New状态
-
就绪状态(Runnable):当你调用线程的
start()
方法时,线程进入“就绪”状态,表示线程已经准备好,等待系统分配CPU时间执行。t.start(); // 线程进入Runnable状态,等待调度
-
运行状态(Running):线程获得CPU时间片并开始执行。在Java中,线程可以通过
Thread.sleep()
等方法主动放弃执行时间片,转入“阻塞”状态。public void run() {System.out.println("线程开始执行..."); }
-
阻塞状态(Blocked):线程在等待某个资源(例如I/O操作或获取锁)时,会进入“阻塞”状态。阻塞状态的线程会等待直到资源可用或获得锁。
synchronized (this) {// 当前线程可能被阻塞// 等待锁 }
-
终止状态(Terminated):线程执行完毕或者由于异常终止时,进入“终止”状态。线程一旦结束,它就不能再重新启动。
// 线程执行完毕,进入Terminated状态
线程调度
线程调度是指操作系统决定哪个线程获得CPU时间片的过程。在Java中,线程调度依赖于操作系统,但Java也允许我们为线程设置优先级来影响调度的顺序。线程的优先级范围是1到10,其中1表示最低优先级,10表示最高优先级。Java中通过Thread.setPriority()
方法来设置线程优先级。
Thread t = new Thread();
t.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级
需要注意的是,线程优先级并不总是能准确影响线程执行顺序,这取决于底层操作系统的调度策略。
线程安全
多线程编程中的一个核心问题是线程安全。当多个线程并发访问共享资源时,必须确保资源的访问不会导致数据不一致或程序崩溃。常见的线程安全问题包括:
-
竞态条件(Race Condition):多个线程并发访问同一资源时,程序的执行结果依赖于线程的执行顺序,导致结果不确定。比如,两个线程同时修改共享变量,可能导致数据丢失或错误。
int count = 0; // 线程1执行 count++ // 线程2执行 count++ // 竞态条件发生
-
死锁(Deadlock):当多个线程互相等待对方释放资源时,形成了死锁,导致程序无法继续执行。
synchronized (lock1) {synchronized (lock2) {// 代码块} } synchronized (lock2) {synchronized (lock1) {// 代码块} }
-
资源竞争(Resource Contention):多个线程在没有适当同步的情况下争夺资源,可能导致数据一致性问题。比如在更新数据库记录时,如果没有同步控制,可能导致多个线程同时修改同一数据。
为了解决线程安全问题,Java提供了多种方法,如使用synchronized
关键字、ReentrantLock
等工具类来进行线程同步。
线程池:Executor框架、线程池的管理与调优
在Java中,线程池是管理线程并发执行的重要工具。使用线程池可以避免频繁创建和销毁线程的开销,提升系统性能,尤其是对于高并发的应用程序来说,线程池是非常必要的。Java提供了Executor
框架,用于简化线程池的创建和管理。
Executor框架
Executor
框架是Java中用于管理线程池的核心框架。它包含了几个接口和类,用于创建和管理线程池。Executor
框架最常用的实现类是ThreadPoolExecutor
,它提供了许多灵活的配置选项,能够满足不同场景下的需求。
常见的线程池类型有:
-
FixedThreadPool:一个固定大小的线程池,适用于负载稳定、任务数目确定的场景。
ExecutorService executorService = Executors.newFixedThreadPool(10); executorService.submit(() -> {System.out.println("Task executed by fixed thread pool."); });
-
CachedThreadPool:一个线程池,它会根据任务量动态创建线程,适合任务量非常大,但每个任务执行时间较短的场景。
ExecutorService executorService = Executors.newCachedThreadPool(); executorService.submit(() -> {System.out.println("Task executed by cached thread pool."); });
-
SingleThreadExecutor:一个单线程池,适合顺序执行任务的场景。
ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(() -> {System.out.println("Task executed by single thread pool."); });
线程池的管理与调优
要充分发挥线程池的性能,合理的线程池配置和调优是必不可少的。线程池的主要参数有:
-
核心线程数(corePoolSize):线程池中始终保持的线程数。如果线程池中的线程数低于核心线程数,即使没有任务,线程池也会创建新的线程。
-
最大线程数(maximumPoolSize):线程池可以创建的最大线程数。当队列满时,线程池会根据配置的最大线程数创建新线程,处理更多的任务。
-
空闲线程存活时间(keepAliveTime):线程池中的线程在空闲时保持活跃的时间。超过这个时间的线程会被销毁。
-
任务队列(workQueue):存放等待执行任务的队列。可以选择不同类型的队列来管理任务的提交。
通过调整这些参数,可以让线程池适应不同的工作负载,提升并发执行的效率。
并发工具类:ReentrantLock、CountDownLatch、CyclicBarrier等
Java还提供了丰富的并发工具类,帮助我们更高效地进行线程同步和协作。这些工具类可以帮助我们解决一些复杂的同步问题,而不需要过多地依赖synchronized
关键字。
ReentrantLock
ReentrantLock
是一个显式锁,相较于synchronized
,它提供了更灵活的锁操作,如尝试加锁、可中断的加锁、锁的公平性等。它适用于需要精确控制线程同步的场景。
ReentrantLock lock = new ReentrantLock();
lock.lock(); // 获取锁
try {// 执行临界区代码
} finally {lock.unlock(); // 释放锁
}
CountDownLatch
CountDownLatch
是一种同步工具,用于让一个线程等待多个线程完成任务。它通过一个计数器来控制,计数器的值会随着每个线程的任务完成而减少,当计数器为0时,等待的线程就可以继续执行。
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {new Thread(() -> {// 执行任务latch.countDown(); // 任务完成,计数器减1}).start();
}
latch.await(); // 等待计数器为0
System.out.println("All threads finished.");
CyclicBarrier
CyclicBarrier
用于在多个线程达到某个共同点时,使得所有线程同步执行。适用于需要多线程分段执行的场景。
CyclicBarrier barrier = new CyclicBarrier(3, () -> {System.out.println("All threads have arrived at the barrier.");
});
for (int i = 0; i < 3; i++) {new Thread(() -> {try {// 执行任务barrier.await(); // 等待其他线程到达屏障} catch (Exception e) {e.printStackTrace();}}).start();
}
小结
在Java的并发编程中,我们涉及了线程生命周期、线程池管理、并发工具类等多个方面。通过合理使用这些技术,我们可以创建高效、线程安全的并发应用程序。然而,线程的使用是非常精细的,必须小心调优和管理,避免常见的并发问题(如死锁、竞态条件等)。希望通过这篇文章,大家能够对Java并发编程有更深入的理解,也能在实际开发中更自如地应用这些技术。
… …
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
… …
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。
版权声明:本文由作者原创,转载请注明出处,谢谢支持!