1、继承Thread类的方式
- 创建一个继承于Thread类的子类
- 重写Thread类中的run(),run()方法也称为线程执行体
- 创建Thread子类的实例,也就是创建了线程对象
- 启动线程,即调用线程的start()方法
public class TestThread extends Thread {
@Override
public void run() {
System.out.println("线程开始执行了");
}
public static void main(String[] args){
Thread t = new TestThread();
t.start();
}
}
2、实现Runnable接口创建线程
- 创建一个实现Runnable接口的类
- 实现Runnable接口中的抽象方法:run(),将创建的线程要执行的操作声明在此方法中
- 创建Runnable接口实现类的实例对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 调用线程对象的start()方法来启动线程
public class TestThread implements Runnable {
@Override
public void run() {
System.out.println("线程开始执行了");
}
}
public class TestMain{
public static void main(String[] args){
Thread t = new Thread(new TestThread());
t.start();
}
}
3、使用Callable和Future创建线程
从Java5开始,Java提供了Callable接口,和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
call()方法可以有返回值
call()方法可以声明抛出异常
-
创建Callable接口的实现类,实现call() 方法
-
创建Callable实现类实例,通过FutureTask类来包装Callable对象,该对象封装了Callable对象的call()方法的返回值。
-
将创建的FutureTask对象作为target参数传入,创建Thread线程实例并启动新线程。
-
调用FutureTask对象的get方法获取返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableThreadTest implements Callable<Integer>
{
@Override
public Integer call() throws Exception
{
int i = 0;
System.out.println("线程开始执行了");
return i;
}
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest ();
FutureTask<Integer> ft = new FutureTask<>(ctt);
new Thread(ft ).start();
String s = ft .get();
System.out.println(s);
}
}
4、使用线程池例如用Executor框架
JDK1.5开始引入了Executor框架用于管理线程和调度线程,可以参考Executor框架的两级调度模型
4.1、使用线程池的好处
降低资源消耗。
每次new Thread()和destroy一个Thread都会耗费一定的系统资源,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。
当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。
线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
4.2、Executor框架的结构
Executor是一个顶级的接口,接口只有execute一个方法
ExecutorService是Executor的子接口,拥有更多需要用的的方法规范
ExecutorService拥有两个重要实现类ThreadPoolExecutor和ScheuledThreadPoolExecutor
ScheduledExecutorService实现了ExecutorService接口
ScheduledThreadPoolExecutor实际是继承了ThreadPoolExecutor并实现ScheduledExecutorService
Executor框架主要成员的介绍:
Executor接口:Executor框架的基础,将任务的提交和任务的执行分离。
ThreadPoolExecutor类:Java线程池的核心实现类,用来执行提交的任务。
ScheduledThreadPoolExecutor类:Executor框架的另一个关键类,可以在给定的延迟后执行命令,或者定期执行命令。
还有Future接口、FutureTask类、Runnable接口、Callable接口。
4.3、ThreadPoolExecutor详解
线程池实现类ThreadPoolExecutor是Executor 框架最核心的类
创建线程的两种方式
1、通过构造方法实现(推荐)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1.1、参数解释
参数 | 解释 | 备注 |
---|---|---|
int corePoolSize | 该线程池中核心线程数最大值 | 线程池新建线程时,如果当前线程总数小于corePoolSize, 则新建的是核心线程,如果超过corePoolSize, 则新建的是非核心线程,核心线程默认情况下会一直存活在线程池中, 即使这个核心线程啥也不干(闲置状态)。 如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true, 那么核心线程如果不干活(闲置状态)的话, 超过一定时间(时长下面参数决定),就会被销毁掉。 |
int maximumPoolSize | 该线程池中线程总数最大值 | 线程总数 = 核心线程数 + 非核心线程数。 |
long keepAliveTime | 该线程池中非核心线程闲置超时时长 | 一个非核心线程,如果闲置状态的时长 超过这个参数所设定的时长,就会被销毁掉, 如果设置allowCoreThreadTimeOut = true, 则会作用于核心线程。 |
BlockingQueue workQueue | 该线程池中的任务队列:维护着等待执行的Runnable对象 | 当所有的核心线程都在干活时, 新添加的任务会被添加到这个队列中等待处理, 如果队列满了,则新建非核心线程执行任务。 常用的workQueue类型有四种 |
ThreadFactory threadFactory | 创建线程的方式 | 这是一个接口,在new操作的时候需要实现他的Thread , 即newThread(Runnable r)方法 |
RejectedExecutionHandler handler | 当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理 | jdk1.5提供了四种饱和策略 |
四种workQueue类型
SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
四种饱和策略
AbortPolicy 默认。直接抛异常。
CallerRunsPolicy 只用调用者所在的线程执行任务,重试添加当前的任务,它会自动重复调用execute()方法
DiscardOldestPolicy 丢弃任务队列中最久的任务。
DiscardPolicy 丢弃当前任务。
2、通过Executor 框架的工具类Executors(不推荐)
1、在《阿里巴巴Java开发手册》“并发处理”这一章节,明确指出线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。
2、《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
Executors 返回线程池对象的弊端如下:
FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
2.1、FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点:
1、适用固定线程数的线程池,适用于为了满足资源管理的需求,而限制当前线程数的应用场景。或者是适用于负载比较重的服务器。
2、FixedThreadPool使用无界队列 LinkedBlockingQueue(队列的容量为Intger.MAX_VALUE)作为线程池的工作队列,可能堆积大量的请求,从而导致OOM。
2.2、SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
特点:
1、适用单个线程的线程池,适用于需要保证各个任务顺序执行,而且在任意时刻,不会有多个活动线程的应用场景。
2、SingleThreadExecutor使用无界队列作为线程池的工作队列会对线程池带来的影响与FixedThreadPool相同。
2.3、CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点
1、根据需要创建新线程的线程池,适用于执行很多短期异步任务的小程序,或者是负载比较轻的服务器。
2、如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新的线程。极端情况下,这样会导致耗尽cpu和内存资源。
3、ThreadPoolExecutor使用案例demo
import java.util.concurrent.*;
public class TreadTest{
public static void main(String[] args) {
//创建一个线程池对象
/**
* 参数信息:
* int corePoolSize 核心线程大小
* int maximumPoolSize 线程池最大容量大小
* long keepAliveTime 线程空闲时,线程存活的时间
* TimeUnit unit 时间单位
* BlockingQueue<Runnable> workQueue 任务队列。一个阻塞队列
*/
ThreadPoolExecutor pool = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10));
//执行任务
for (int i = 0; i < 10; i++) {
int index = i;
pool.execute( ()-> System.out.println("i:"+index+"execute!"));
}
//关闭线程池
pool.shutdown();
}
}
5、ScheduledThreadPoolExecutor详解
ScheduledThreadPoolExecutor主要用来在给定的延迟后运行任务,或者定期执行任务。
5.1、与ThreadPoolExecutor的区别
使用DelayQueue作为阻塞队列
获取任务的方式不同
执行周期任务后,添加了额外的处理
5.2、ScheduledThreadPoolExecutor执行周期任务的步骤
1、线程1从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask的time大于等于当前系统的时间;
DelayQueue中封装的PriorityQueue,会对这些任务按time进行排序,时间小的先被执行。如果time相同,则按seq进行排序,序号较小的先执行。(即如果任务的time相同,则先提交的先被执行)
2、线程1执行这个ScheduledFutureTask;
ScheduledFutureTask中的三个重要的成员变量
long time: 表示这个任务将要被执行的具体时间。当time大于等于当前系统的时间,表明该任务已到期,可以执行。
long sequenceNumber: 表示这个任务被添加到ScheduledThreadPoolExecutor的序号。
long period: 表示任务执行的时间间隔
3、线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间;
4、线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。
5.3、ScheduledThreadPoolExecutor和Timer和Quartz的比较
Timer只有一个执行线程,因此长时间运行的任务可以延迟其他任务
TimerTask中抛出的运行时异常会导致计划任务将不再运行
ScheduledThreadExecutor不仅可以捕获运行时异常,还可以处理异常,其他任务的允许不会停止。
ScheduledThreadPoolExecutor可以配置任意数量的线程,不会导致长时间允许一个任务而延迟其它任务。
PS:相对于两者,一般定时任务还是使用Quartz居多。
5.4、ScheduledThreadPoolExecutor使用案例
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TreadTest {
private static class TaskDemo implements Runnable {
private String TAG = "";
public TestTask(String tag) {
TAG = tag;
}
@Override
public void run() {
System.out.println(TAG + "\t" + System.currentTimeMillis());
}
}
public static void main(String[] args) {
ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(2);
TaskDemo task1 = new TaskDemo ("第一个任务");
TaskDemo task2= new TaskDemo ("第二个任务");
// 延迟10秒执行
threadPoolExecutor .schedule(task1, 10, TimeUnit.SECONDS);
// 延迟20秒执行
threadPoolExecutor .schedule(task12, 20, TimeUnit.SECONDS);
}
}
如果你觉得本篇文章对你有所帮助的话,麻烦请点击头像右边的关注按钮,谢谢!
技术在交流中进步,知识在分享中传播