常见的 Java 线程的 4 种创建方式分别为:继承 Thread 类、实现 Runnable 接口、通过ExecuteService 和 Callable 实现有返回值得线程、基于线程池。如下图所示:
Thread 类实现了 Runnable 接口并定义了操作线程的一些方法,我们可以通过继承 Thread 类的方法创建一个线程。具体实现为创建一个类并继承 Thread 接口,然后实例化线程对象并调用 start 方法启动线程。
start 方法是一个 native 方法,通过在操作系统上启动一个新线程,并最终执行 run 方法来启动一个线程。run 方法内的代码是线程类的具体实现逻辑。代码如下:
//用过继承Thread类创建NewThread线程
public class NewThread extends Thread{
@Override
public void run(){
System.out.println("create a thread by extends Thread");
}
}
//实例化一个NewThread线程对象
NewThread newThread = new NewThread();
//调用 start 方法启动 NewThread 线程
newThread.start();
基于 Java 编程语言的规范,如果子类已经继承(extends)了一个类,就无法继承 Thread 类,此时可以通过实现 Runnable 接口创建线程。具体的实现过程为:
通过实现 Runnable 接口 ===> 创建 ChildrenClassThread 线程,实例化名称为 childrenThread 的线程实列 ===> 创建 Thread 类的实列并传入 childrenThread 线程实例 ===> 调用线程的 start 方法启动线程。实现代码如下:
//通过实现 Runnable 接口方式创建 ChildrenClassThread 线程
public class ChildrenClassThread extends SuperClass implements Runnable {
@Override
public void run() {
System.out.println("create a thread by implements Runnable");
}
}
// 实例化一个 ChildrenClassThread
ChildrenClassThread childrenClassThread = new ChildrenClassThread();
//创建一个线程对象并将其传入已经实例化好的 childrenThread
Thread thread = new Thread(childrenClassThread);
// 调用 start 方法启动一个线程
thread.start();
事实上,在传入一个实现了 Runnable 的线程实例 target 给 Thread后,Thread 的run 方法在执行时就会调用 target,run 方法并执行该线程具体的实现逻辑。
在 JDK 源码中, run 方法的实现代码如下:
@Override
public void run(){
if(target != null){
target.run();
}
}
有时,我们需要在主线程中开启多个线程并发执行一个任务,然后收集各个线程执行的结果并将最终结果汇总起来,这时就要用到 Callable 接口。
具体实现方法为:创建一个类并实现 Callable 接口,在 call 方法中实现具体的运算逻辑并返回计算结果。(具体调用流程为:创建一个线程池,一个用于接收返回结果的 Future List 及 Callable 线程实例,使用线程池提交并将线程执行之后的结果保存在 Future 中,在线程执行结束后遍历 Future List 中的 Future 对象,在该对象上调用 get 方法就可以获取 Callable 线程任务返回数据并汇总结果。)
代码流程:
/**
* @Description: 通过实现 Callable 接口创建 MyCallable
*
* @param null
* @return: {@link null}
* @Author: 甘秦胜
* @Date: 2020/8/30 21:44
*/
public class MyCallable implements Callable<String> {
private String name;
public MyCallable(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
return name;
}
}
//创建一个固定大小为 5 的线程池
ExecutorService pool= Executors.newFixedThreadPool(5);
// 创建多个有返回值的任务列表 list
List<Future> list=new ArrayList<Future>();
for(int i = 0 ; i < 5 ; i++){
//创建一个有返回值的线程实例
MyCallable c = new MyCallable(i + " ");
//提交线程,获取 Future 对象并将其保存到 Future List 中
Future future = pool.submit(c);
System.out.println("submit a callable Callable thread:" + i);
list.add(future);
}
//关闭线程池,等待线程执行结束
pool.shutdown();
//遍历所有的线程的运行结果
for(Future f : list){
//从 Future 对象上获取任务的返回值,并将其结果输出到控制台
System.out.println("get the result from cllable thread:"+ f.get().toString());
}
1.4 基于线程池
线程是非常宝贵的计算资源,在每次需要时创建并在运行结束后销毁是非常浪费资源的。我们就可以使用缓存策略并使用线程池来创建线程,具体过程为创建一个线程池并用该线程池提交线程任务,实现代码如下:
public class ThreadTest {
public static void main(String[] args) throws Exception {
//创建大小为 10 的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
//提交多个线程任务并执行
for (int i = 0; i < 10; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "is Running");
}
});
}
}
}
原理:
JVM 先根据用户的参数创建一定的数量的可运行的线程任务,并将其放入列队中,在线程创建后启动这些任务,如果线程数量超过了最大线程数量(用户设置的线程池的大小),则超出数量的线程排队等待,在有任务执行完毕后,线程池调度器会发现有可用的线程,进而再次从列队中取出任务并执行。
作用:
线程池的主要作用是线程复用、线程资源管理、控制操作系统的最大并发数,以保证系统高效(通过线程资源复用实现)且安全(通过控制最大线程并发数实现)地运行。
在 Java 中,每个 Thread 类都一个 start 方法。在程序调用 start 方法启动线程时, Java 虚拟机会调用该类的 run 方法。
在 Thread 类的 run 方法中其实调用了 Runnable 对象中的 run 方法,因此可以继承 Thread 类,在 start 方法中不断循环调用传递进来的 Runnable 对象,程序就会不断执行 run 方法中的代码。可以将在循环方法中不断获取的 Runnable 对象放在 Queue 中,当前线程在获取一个 Runnable 对象之间可以是阻塞的,这样既能有效控制正在执行的线程个数,也能保证系统中正在等待执行的其他线程有序执行。
Java 线程主要由以下 4 个核心组件组成:
a. 线程池管理器:用于创建并管理线程池
b. 工作线程:线程池中执行具体任务的线程。
c. 任务接口:用于定义工作线程的调度和执行策略,只有线程实现了该接口,
线程中的任务才能够被线程池调度。
d. 任务列队:存放待处理的任务,新的任务将会不断被加入队列中,
执行完成的任务将被从队列中移除。
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor、Executors、ExecutorService、ThreadPoolExcutor、Callable、Future、FutureTask 这几个核心类,具体的继承关系如下图所示:
其中 ThreadPoolExecutor 是构建线程的核心方法,该放定义如下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue){
this(corePoolSize,maximumPoolSize,keepAliveTime,unit,
workQueue,Executors.defaultThreadFactory(),defaultHandler);
}
ThreadPoolExecutor 构造函数的具体参数如下表:
序号 | 参数 | 说明 |
---|---|---|
1 | corePoolSize | 线程池中核心线程的数量 |
2 | maximumPoolSize | 线程池中最大线程的数量 |
3 | keepAliveTime | 当前线程数量超过 corePoolSize 时,空闲线程的存活时间 |
4 | unit | KeepAliveTime 的时间单位 |
5 | workQueue | 任务队列,被提交但尚未被执行的任务存放地方 |
6 | threadFactory | 线程工厂,用于创建线程,可使用默认的线程工厂或自定义线程工厂 |
7 | handler | 由于任务过多或者其他原因导致线程池无法处理时的任务拒绝策略 |