概述
Java 创建线程有四种方式
- 继承Thread类,重写start方法
- 实现Runnable接口,重写run方法,并使用Thread类启动
- 实现Callable接口,重写call方法,并使用Thread类启动
- 运用线程池
1 继承Thread类
package com.mingweicai.thread.newthread;
class MyThread extends Thread{
@Override
public void run() {
System.out.println("------ new Thread name ------" + Thread.currentThread().getName());
}
}
public class Main1 {
public static void main(String args[]){
System.out.println("------ main Thread name ------" + Thread.currentThread().getName());
new MyThread().start();
}
}
输出如下
------ main Thread name ------main
------ new Thread name ------Thread-0
2 继承Runnable接口
package com.mingweicai.thread.newthread;
public class Main2 {
public static void main(String args[]){
System.out.println("------ main Thread name ------" + Thread.currentThread().getName());
Runnable runnable = () -> {
System.out.println("------ new Thread name ------" + Thread.currentThread().getName());
};
new Thread(runnable).start();
}
}
输出如下
------ main Thread name ------main
------ new Thread name ------Thread-0
3 继承Callable接口
Callable 允许线程执行完之后返回一个结果值,它和Runnable的主要区别如下
- Callable 和 Runnable都是一个接口
- Callable 允许线程执行完之后返回一个值,而Runnable接口没有返回值
- Callable 要配合FutureTask使用接收返回值
使用方式如下代码所示
package com.mingweicai.thread.newthread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main3 {
public static void main(String args[]) throws ExecutionException, InterruptedException {
// 创建Callable接口
Callable<String> callable = () -> {
return "result is : hello world";
};
// 新建FutureTask
FutureTask<String> futureTask = new FutureTask<String>(callable);
// 启动线程
new Thread(futureTask).start();
// 等待线程执行完成获取结果
String result = futureTask.get();
System.out.println("result is : " + result);
}
}
// 输出如下
result is : hello world
4 运用线程池
什么是线程池,所谓池,就是一个容器,里面装着一些东西,那么线程池也是类似的,可以抽象的理解成线程池里面存放着很多线程,当你新建一个任务需要执行的时候,只要在线程池里面拿出一条线程来执行就可以了,从而避免了频繁创建新线程等复杂的操作。
线程池一个最全的构造方法如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
参数解析如下
corePoolSize 最大核心线程数,提交任务到线程池时,如果线程池中线程数小于最大核心线程数,那么线程池会新建一个线程来处理任务,而不管池中其他线程是否空闲
maximumPoolSize 最大线程数,提交任务到线程池时,如果线程池中线程数大于corePoolSize,而且任务队列已满,并且线程池中线程数小于maximumPoolSize,那么线程池会新建一个线程来处理任务
keepAliveTime 存活时间,非核心线程存活时间
unit 时间单位(秒,分钟,小时等等)
workQueue 任务队列,当提交任务时线程池中线程数等于corePoolSize,那么线程池会把任务放到一个阻塞队列中,空闲线程回到队列获取任务执行
threadFactory 线程工厂,默认即可
handler 饱和策略
可能一下子无法理解这些参数,结合图解过程来理解
假设此时corePoolSize = 2 、maximumPoolSize = 5、workQueue = new ArrayBlockingQueue(5) 长度是5,handler 是默认饱和策略。
(1)刚开始新建线程池的时候,线程池中是没有线程的,如图1-1所示
(2)下次有个任务来了,这个时候线程池发现线程池中线程数是0,并且0<corePoolSize, 因此线程池新建一条线程,称为核心线程,执行当前任务,再有一个任务来了,此时线程池中线程数为1,1<corePoolSize,因此再创建一条核心线程,并执行当前任务,如图1-2所示。
(3)此时线程池中的线程数等于corePoolSize,那么会把后续任务放到阻塞队列(workQueue )中,由线程到阻塞队列拉取任务执行,如图1-3所示
- LinkedBlockingQueue 基于链表的无界阻塞队列,基于FIFO原则排序元素,吞吐量高于ArrayBlockingQueue
- ArrayBlockingQueue 基于数组的有界组设队列,基于FIFO原则排序元素
- SynchronousQueue 这是一个不存储阻塞元素的阻塞队列,为什么这样说呢,当一个任务提交到这个队列时,必须等待这个任务被线程取走才能继续入队,所以这个队列最多只能有一个任务,吞吐量高于LinkedBlockingQueue
- PriorityBlockingQueue 具有优先级的队列
(4)如果线程池中的线程数等于corePoolSize ,并且当前所有线程都在执行任务,此时线程池没有线程可用了,那么新到来的任务都会放到阻塞队列中,当阻塞队列的任务超过阻塞队列的最大长度时,如果线程池中线程数小于maximumPoolSize,那么就会继续创建线程,这时候创建的线程成为非核心线程,然后拉取阻塞队列中的任务执行,如图1-4所示,1,2号线程是步骤2,3创建出来的线程,此时正在执行任务,3、4、5号线程是新建出来的线程,正在拉取阻塞队列的任务准备执行。
(5)现在线程池中有5条线程,那么有3条非核心线程,可以抽象地理解是3、4、5,当线程池任务不繁忙的时候,这三条非核心线程经过keepAliveTime时间后就会死亡,销毁,有两条核心线程一直在线程池中存活者等待任务执行。
(6)但是当线程池任务提交非常繁忙时,如果线程池五条线程都在执行任务中,而阻塞队列又已经满了,如图1-5所示,那么会触发饱和策略。
- AbortPolicy:直接丢弃并且抛出
RejectedExecutionException
异常,如图1-5,任务13直接丢弃,并且线程池抛出异常。 - CallerRunsPolicy:只用调用者所在线程来运行任务,运用新建线程池的那条线程在处理任务。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务,如图1-5,会丢弃任务12,并且把任务13加入队列中。
- DiscardPolicy:丢弃任务并且不抛出异常。如图1-5,任务13直接丢弃,而且不抛出异常。
最后线程池的流程总结如下图
例子使用如下
package com.mingweicai.thread.newthread;
import java.util.concurrent.*;
public class Main4 {
public static void main(String args[]){
System.out.println("------ main Thread name ------" + Thread.currentThread().getName());
// 核心线程数
int corePoolSize = 1;
// 最大线程数
int maximumPoolSize = 1;
// 阻塞队列
BlockingQueue<Runnable> blockingQueue = new LinkedBlockingDeque<>();
// 非核心线程存货时间
int keepAliveTime = 1000;
// 线程工厂
ThreadFactory threadFactory = Executors.defaultThreadFactory();
// 饱和策略
RejectedExecutionHandler defaultHandler = new ThreadPoolExecutor.AbortPolicy();
// 新建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,maximumPoolSize,keepAliveTime,
TimeUnit.SECONDS,blockingQueue
);
// 执行线程
Runnable runnable = () -> {
System.out.println("------ new Thread name ------" + Thread.currentThread().getName());
};
executor.execute(runnable);
}
}
// 输出如下
------ main Thread name ------main
------ new Thread name ------pool-2-thread-1
Java 中的Executors其实提供了创建四种线程池的方法,但是阿里开发手册中并不推荐使用,因为如果一些人没有清晰地理解线程池运行原理的话会造成滥用的情况,本人也不太喜欢直接用这个类,因此不再详细介绍,如果读者们有兴趣了解线程池源码,可以参考一下线程池源码解析 这篇文章
最后
以上就是活力耳机为你收集整理的面试官问我Java创建线程的方式和线程池参数?我笑了的全部内容,希望文章能够帮你解决面试官问我Java创建线程的方式和线程池参数?我笑了所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复