文章目录
- java如何创建线程
- 继承 `Thread` 类
- 使用 `Runnable` 接口
- 选择继承还是实现接口
- 线程池的核心参数和原理
- 核心参数
- 原理
- 为什么是先添加队列 队列满了在创建最大线程?
在 Java 中,线程的生命周期包括几个不同的状态,这些状态可以由线程的状态机(State Machine)来表示。线程状态的转换遵循一定的规则,并且可以通过 Java 线程 API 来监控和控制。以下是 Java 线程的生命周期及其状态:
- 新建(New)状态:
- 当线程被创建时,它处于新建状态。在这个状态下,线程还没有开始执行。
- 可以通过
start()
方法将线程从新建状态转换为就绪状态。
- 就绪(Runnable)状态:
- 线程调用
start()
方法后,它进入就绪状态。在这个状态下,线程已经准备好执行,但是它还不能立即执行,因为它需要等待操作系统分配CPU时间。 - 线程从就绪状态转换为运行状态,需要依赖于操作系统的调度器。
- 线程调用
- 运行(Running)状态:
- 线程获得CPU时间后,进入运行状态。在这个状态下,线程正在执行。
- 如果线程正在执行,但是由于某些原因(如等待IO操作完成)而暂时无法继续执行,它将转换为阻塞状态。
- 阻塞(Blocked)状态:
- 线程由于等待外部资源(如等待同步锁、等待IO操作完成等)而无法继续执行时,它进入阻塞状态。
- 阻塞状态的线程不会占用CPU时间。当等待的资源可用时,线程将转换为就绪状态。
- 等待(Waiting)状态:
- 线程执行
Object.wait()
方法后,进入等待状态。在这个状态下,线程不会被分配CPU时间,并且不会被操作系统调度。 - 线程从等待状态转换为就绪状态,需要等待其他线程调用
Object.notify()
或Object.notifyAll()
方法。
- 线程执行
- 计时等待(Timed Waiting)状态:
- 线程执行
Thread.sleep()
或Object.wait(long timeout)
方法后,进入计时等待状态。 - 与等待状态类似,计时等待状态的线程不会被分配CPU时间,并且不会被操作系统调度。
- 线程从计时等待状态转换为就绪状态,有两种情况:一种是等待时间到期,另一种是其他线程调用
Thread.interrupt()
方法。
- 线程执行
- 终止(Terminated)状态:
- 线程执行完毕后,进入终止状态。在这个状态下,线程不再运行。
- 一旦线程进入终止状态,它就不能再被启动或恢复。
Java 线程的状态转换遵循一定的规则,并且可以通过 Java 线程 API 来监控和控制。了解线程的生命周期和状态对于编写多线程应用程序非常重要。
java如何创建线程
在 Java 中,有多种方式可以创建线程,包括继承 Thread
类和使用 Runnable
接口。以下是这两种方法的基本步骤:
继承 Thread
类
- 创建一个新类,继承自
Thread
类。 - 重写
run()
方法,在这个方法中编写线程要执行的代码。 - 创建新类的对象。
- 调用
start()
方法,启动线程。
class MyThread extends Thread {public void run() {// 线程要执行的代码System.out.println("Thread is running.");}
}
public class Main {public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); // 启动线程}
}
使用 Runnable
接口
- 创建一个新类,实现
Runnable
接口。 - 实现
run()
方法,在这个方法中编写线程要执行的代码。 - 创建新类的对象。
- 创建
Thread
类的对象,并将步骤2中创建的对象作为参数传递给Thread
类的构造函数。 - 调用
start()
方法,启动线程。
class MyRunnable implements Runnable {public void run() {// 线程要执行的代码System.out.println("Thread is running.");}
}
public class Main {public static void main(String[] args) {MyRunnable runnable = new MyRunnable();Thread thread = new Thread(runnable);thread.start(); // 启动线程}
}
选择继承还是实现接口
- 继承
Thread
类:这种方式相对简单,但是因为 Java 不支持多重继承,所以如果类已经继承自其他类,就不能再继承Thread
类。 - 实现
Runnable
接口:这种方式更灵活,因为一个类可以实现多个接口。
在实际应用中,推荐使用实现Runnable
接口的方式,因为它更符合 Java 的设计哲学,即单一继承原则。
线程池的核心参数和原理
线程池的核心参数和原理涉及线程池的配置和管理,以及它如何有效地处理并发任务。以下是线程池的一些核心参数和原理:
核心参数
- 核心线程数(Core Pool Size):
- 核心线程数是指线程池中始终存在的线程数量,即使这些线程处于空闲状态。
- 即使没有新的任务提交,核心线程数也不会减少。
- 最大线程数(Maximum Pool Size):
- 最大线程数是指线程池能够创建的最大线程数量。
- 当线程池中的线程数达到最大值时,新提交的任务会被加入到队列中等待执行。
- 队列容量(Queue Capacity):
- 队列容量是指线程池中的任务队列可以存储的任务数量。
- 当队列已满时,如果核心线程数未达到上限,线程池会创建新的线程来处理任务;如果核心线程数已达到上限,线程池会拒绝新的任务,并抛出
RejectedExecutionException
。
- 存活时间(Keep-Alive Time):
- 存活时间是指当线程池中的线程数超过核心线程数时,多余的空闲线程将在多长时间后被终止。
- 存活时间可以通过
keepAliveTime
和TimeUnit
参数指定。
- 时间单位(Time Unit):
- 时间单位参数用于指定存活时间的时间单位,例如
TimeUnit.SECONDS
、TimeUnit.MILLISECONDS
等。
- 时间单位参数用于指定存活时间的时间单位,例如
原理
- 任务提交:
- 当有新任务提交时,线程池会检查当前线程数是否小于核心线程数。
- 如果小于,则创建新的线程来执行任务。
- 如果等于或大于核心线程数,则将任务加入到任务队列中。
- 任务执行:
- 如果任务队列未满,新任务会被加入到队列中。
- 如果任务队列已满,线程池会创建新的线程来执行任务,直到线程数达到最大线程数。
- 空闲线程处理:
- 当线程池中的线程数超过核心线程数时,多余的空闲线程会在存活时间内被终止。
- 存活时间可以通过
keepAliveTime
和TimeUnit
参数指定。
- 任务执行完成后:
- 执行完任务的线程会重新进入空闲状态,等待新的任务。
- 如果存活时间到期,空闲线程将被终止。
- 任务拒绝:
- 当任务队列已满,且线程数达到最大线程数时,新的任务会被拒绝,并抛出
RejectedExecutionException
。
线程池的核心原理是通过控制线程数和任务队列的大小,以及管理空闲线程的生命周期,来实现对并发任务的高效处理。通过合理配置线程池的参数,可以有效地平衡系统资源的使用和任务的处理效率。
- 当任务队列已满,且线程数达到最大线程数时,新的任务会被拒绝,并抛出
为什么是先添加队列 队列满了在创建最大线程?
线程池的工作流程设计是先添加任务到队列,当队列满了之后再创建新的线程,这是为了在并发场景下尽可能地提高系统的性能和资源利用率。这种设计理念基于以下几个原因:
- 减少线程创建和销毁的开销:
- 创建和销毁线程是有成本的,因为涉及到内存分配、线程上下文切换等操作。
- 如果每次有新任务到来时都立即创建新线程,那么在任务量较小的情况下,线程创建和销毁的开销可能会超过实际执行任务的收益。
- 提高系统资源的利用率:
- 在任务量不大的情况下,线程池中的线程可能大部分时间都在等待任务。
- 如果任务队列满了,那么新任务的到来可以唤醒等待中的线程,而不是创建新的线程。这样可以提高CPU和其他系统资源的利用率。
- 避免线程数过多:
- 如果每次有新任务到来时都创建新线程,可能会导致线程数过多,进而导致系统资源不足,如内存溢出。
- 通过先添加任务到队列,可以限制线程数的增长,从而避免资源过度消耗。
- 线程池的动态调整:
- 线程池可以根据任务量和系统负载动态调整线程数。
- 当任务量增加时,线程池会创建新的线程来处理任务;当任务量减少时,空闲线程会在存活时间内被终止,从而减少线程数。
- 避免频繁的线程创建和销毁:
- 频繁的线程创建和销毁会导致系统性能下降,因为线程上下文切换的开销较大。
- 通过使用任务队列作为缓冲区,可以减少这种开销,让线程有更多的时间来执行任务。
因此,线程池的设计理念是先使用任务队列作为缓冲,以减少线程创建和销毁的开销,并提高系统资源的利用率。当任务队列满了之后,再创建新的线程来处理任务,这样可以更好地平衡系统的性能和资源利用率。