🚀Java线程&线程池
🚀Java线程&线程池
1.Java线程
线程生命周期
生命周期:新建=》就绪=》启动=》阻塞=》死亡
1.新建(New):创建后未启动(未调用start()方法)的线程处于这种状态
2.就绪(Ready):调用start()方法,进入就绪状态。等待CPU调度执行
3.运行(Running):该状态可能正在CPU执行,也可能在等待CPU执行
注意: 线程要想进入运行状态执行,首先必须处于就绪状态中
4.无限期等待(Waiting):不会分配CPU执行时间,要等待被其他线程显式唤醒。Object.wait()方法可让其无限等待
5.限期等待(Timed Waiting): 不会分配CPU执行时间,在一定时间之后它们会由系统自动唤醒。Object.wait(long millis)方法让线程进入限期等待状态
6.阻塞(Blocked):线程在获取排他锁失败(因为锁被其它线程所占用),进入同步阻塞状态
7.死亡(Dead):线程执行完了或者因异常退出了run()方法,该线程生命周期结束
创建线程
线程的创建有三种方式:继承Thread、实现Runnable、实现Callable接口
- 继承Thread类,重写run方法,调用start方法启动线程
class MyThread1 extends Thread {
@Override
public void run() {
while(true){
System.out.println("方式1创建线程......");
}
}
}
public class CreateThreadDemo1 {
public static void main(String[] args) {
MyThread1 mt = new MyThread1();
mt.start();
}
}
- 实现Runnable接口,重写run方法,通过Thread调用start方法启动线程
class MyThread2 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("方式2创建线程......");
}
}
}
public class CreateThreadDemo2 {
public static void main(String[] args) {
MyThread2 mt = new MyThread2();
Thread thread = new Thread(mt);
thread.start();
}
}
- 实现Callable接口,重写call方法(从任务中产生返回值),借助FutureTask对象创建间Thread对象启动线程,调用get方法获取线程结束之后的结果
class MyThread3 implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("方式3:借助Callable创建线程" + i);
}
return "call线程";
}
}
public class CreateThreadDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 线程开启之后需要执行里面的call方法
MyThread3 mt = new MyThread3();
// 构建FutureTask对象
FutureTask<String> ft = new FutureTask<>(mt);
//创建线程对象
Thread t = new Thread(ft);
// 开启线程
t.start();
System.out.println(ft.get());
}
}
测试类
当线程调用start方法之后会进入就绪ready状态(而非运行run状态),线程内部依赖jvm的调度,当线程调用start方法之后,jvm会判定这个线程是否可执行,但至于什么时候执行则取决于jvm内部的调度,由CPU自行决定要指定的线程
public class ThreadTest {
/**
* 1>分别以两种方式测试线程创建,分析其生命周期
* a.继承Thread类
* b.实现Runnable接口
*
* 2>线程的5个状态分析
* 新建 new
* 就绪 ready
* 运行 run
* 阻塞 wait
* 死亡 terminal(结束)
*/
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 方式1创建线程:继承Thread
MyThread1 mt = new MyThread1();
mt.start();
// 方式2创建线程:实现Runnable接口(可通过Lambda表达式创建并启动线程)
new Thread(new Runnable() {
@Override
public void run() {
while(true){
System.out.println("方式2创建线程:借助Lambda表达式构建");
}
}
}).start();
/**
* 执行结果分析:线程的启动主要CPU内部的调度(涉及CPU时间片的概念)
* 此处执行的结果显示,每次执行的线程顺序均有所不同,
* 且每个线程是交替执行的,一个线程执行一段时间之后便将CPU交付给下一个线程执行以此类推
*/
}
}
🔖线程设置
常用的线程方法
方法名 | 说明 |
---|---|
void setName(String name) | 设置线程名称 |
String getName() | 返回线程名称 |
Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
线程是一个可调操作系统的对象,他的start()、stop()、yield()、sleep(long var0)...等线程方法都是调用操作系统执行
public class Thread implements Runnable {
private long tid; 线程的ID, 由线程自己分配
private volatile String name; 线程的名称, 可以手动设置(setName())
private int priority; 线程的优先级, 默认5, 最小1, 最大10
private ThreadGroup group; 线程所属的线程组
private ClassLoader contextClassLoader; 次线程上下文ClassLoader
private boolean daemon = false; 是否是守护线程
private volatile int threadStatus = 0; 线程的状态. 对应静态枚举类Stete
public void run(){} 线程的功能方法
public synchronized void start(){} 线程准备就绪,等待程序调用run()方法. 异步操作, 调用顺序不代表实际启动的顺序.
public final void stop(){} 终止当前线程, 不建议使用
public static native void sleep(long var0){} 指定毫秒数让正在执行的线程暂停, 具体取决于系统定时器和调度程序的精度和准确性
public final void setPriority(int var1){} 设置优先级, 不一定有用, 影响线程执行优先级的因素很多
public final void setDaemon(boolean var1){} 设置守护线程标识
public static native void yield(){} 放弃当前CPU执行资源,放弃时间不确定
public final native boolean isAlive(){} 判断线程是否处于活动状态. 活动状态: 启动未终止
public final void resume(){} 恢复线程, 不建议使用
public final void suspend(){} 暂停线程, 不建议使用
public void interrupt(){} 中断线程
public static boolean interrupted(){} 测试当前线程是否中断, 并且具备清除状态的功能
public boolean isInterrupted(){} 测试当前线程是否中断
public final void join(){} 等待调用的这个线程终止
public Thread.State getState(){} 获取线程状态
}
🚀不同创建方式的对比
线程有几种创建方式? 这几种创建方式有和优缺点有何区别?
采用实现Runnable接口的方式
线程仅仅是实现了Runnable接口,还可以继承其他类
在这种方式下,可以多个线程共享一个Target对象,非常适合多个线程共同处理同一份资源,从而将cpu代码、数据分开,较好的体现了面向对象的思想
劣势:编程稍微复杂,如果要访问当前线程,必须使用Thread.currentThread()方法
采用继承Thread类的方式
劣势:因为线程继承了Thread类,所有不能再继承其他父类
优势:编程比较简单,如果需要访问当前线程,直接使用this对象即可
采用实现Callable接口的方式
劣势:编程相对复杂,不能直接使用Thread类中的方法(需借助第三方对象进行构建)
优势:扩展性强,实现该接口的同时还可继承其他的类
👀线程常用方法
1.Thread.sleep(long millis):当前线程调用此方法,进入阻塞,不释放对象锁,线程自动唤醒进入可运行状态
2.Thread.yield():当前线程调用此方法,放弃获取的cpu时间片,由运行状态变可运行状态,让OS再次选择线程。实际中无法保证yield()达到让步目的,Thread.yield()不会导致阻塞
3.Thread.join()/thread.join(long millis):当前线程里调用其它线程的join方法,当前线程阻塞,但不释放对象锁,待别的线程执行结束,当前线程进入可运行状态
4.Obj.wait():当前线程调用释放对象锁(需先拿锁),进入等待队列
5.Obj.notify():唤醒在此对象监视器上等待的单个线程,选择是任意性的
6.Obj.notifyAll():唤醒在此对象监视器上等待的所有线程
2.线程池
线程池简介
为什么引入线程池?
1.降低资源消耗:重复利用已创建的线程,降低线程创建和销毁的损耗
2.提高响应速度:务到来时能立即执行
3.提高线程的可管理性:使用线程池可统一的分配、调优和监控
4.提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。如延时定时线程池ScheduledThreadPoolExecutor,允许任务延期执行或定期执行
线程池设计
设计 | 说明 |
---|---|
Executor | 顶层接口,提供思想,将任务提交和任务执行进行解耦 |
ExecutorService | 接口继承 Executor,扩展了生命周期管理的方法、返回 Futrue 的方法、批量提交任务的方法 |
AbstractExecutorService | 抽象类继承 ExecutorService 接口,对 ExecutorService 相关方法提供了默认实现,用 RunnableFuture 的实现类 FutureTask 包装 Runnable 任务,交给 execute() 方法执行,然后可以从该 FutureTask 阻塞获取执行结果,并且对批量任务的提交做了编排 |
ThreadPoolExecutor | 继承 AbstractExecutorService,维护自身的生命周期,管理线程和任务 |
线程池的运行状态
Running:能接受新提交的任务,并且也能处理阻塞队列里的任务
Shutdown:关闭状态,不再接受新提交的任务,但可以继续处理阻塞队列里的任务
Stop:不能接受新任务,也不能处理队列里的任务,会中断正在处理任务的线程
Tidying:所有任务都终止,workerCount(有效线程数)为0
Terminated:在terminated()方法执行后进入此状态。处于SHUTDOWN或STOP状态,且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态
ThreadPoolExecutor 常用方法:
submit():执行 Callable
execute():执行 Runnable
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,不接受新任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
isShutdown():判断线程是否终止
getActiveCount():正在运行的线程数
getCorePoolSize():获取核心线程数
getMaximumPoolSize():获取最大线程数
getQueue():获取线程池中的任务队列
allowCoreThreadTimeOut(boolean):设置空闲时是否回收核心线程
线程池参数及配置
(1)7大参数
1.核心线程数(corePoolSize)
线程池中的核心线程数量(最少的线程个数),即使这些线程处理空闲状态,也不被销毁,除非设置了 allowCoreThreadTimeOut; 默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程
2.任务队列(workQueue)
任务队列,当核心线程全部繁忙时,由 execute/submit 方法提交的 Runnable 任务存放到该任务队列中,等待被核心线程来执行
3.最大线程数(maximumPoolSize)
线程池中允许的最大线程数,当核心线程全部繁忙且任务队列存满之后,线程池会临时追加线程,直到总线程数达到 maximumPoolSize
4.线程空闲超时时间(keepAliveTime)
如果一个线程处于空闲状态,并且当前的线程数量大于 corePoolSize,那么在指定时间后,这个空闲线程会被销毁
5.时间单位(unit)
keepAliveTime 的时间单位(天、小时、分、秒......),java.util.concurrent.TimeUnit
6.线程工厂(threadFactory)
用于创建线程,可默认也可以自定义实现,可以用来指定线程名,是否为daemon线程等
7.拒绝策略(handler)
核心线程 corePoolSize 正在执行任务、任务队列workQueue 已满、并且线程池中的线程数达到 maximumPoolSize 时,就需"拒绝"掉新提交过来的任务
(2)拒绝策略
AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常
CallerRunsPolicy:直接运行这个任务的run方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理
DiscardPolicy:直接丢弃任务,不抛出任何异常
DiscardOldestPolicy:将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行
(3)7个阻塞队列
ArrayBlockingQueue:数组有界,FIFO排序,支持公平锁和非公平锁
LikedBlockingQueue:链表有界,FIFO排序,默认长度为Integer.MAX_VALUE
PrioriBlockQueue:线程优先级排序无界队列,默认自然排序,也可实现compareTo()方法排序
DelayQueue:延时获取无界,可指定多久后才可以获取元素
SynchronousQueue:不储存元素,每次put必须等待take,否则不能添加元素,支持公平锁和非公平锁
LinkedTransferQueue:链表无界,多了transfer()、tryTransfer()在队尾插元素方法
LinkedBlockingDeque:链表双向,头尾都可添加移除元素,可降低锁的竞争
(4)Executors提供的线程池
FixedThreadPool(n)
数量固定的线程池,超出的任务会在队列中等待空闲的线程,可用于控制程序的最大并发数
CachedThreadPool()
短时间内处理大量工作的线程池,会根据任务数量产生对应的线程,并试图缓存线程以便重复使用,如果限制 60 秒没被使用,则会被移除缓存
SingleThreadExecutor()
创建一个单线程线程池
ScheduledThreadPool(n)
创建一个数量固定的线程池,支持执行定时性或周期性任务
SingleThreadScheduledExecutor()
此线程池就是单线程的 newScheduledThreadPool
WorkStealingPool(n)
Java8 新增创建线程池的方法,创建时如果不设置任何参数,则以当前机器处理器个数作为线程个数,此线程池会并行处理任务,不能保证执行顺序
(5)线程池大小的选择
CPU 密集型: 任务需要大量使用 CPU 进行运算,但是没有阻塞,CPU一直高速运行;该情况下应尽可能减少 CPU 对线程的切换,所以要使线程数尽可能少;
线程数 = CPU 核心数 + 1;
IO 密集型: 任务需要进行大量的 IO 操作,那么就会有大量的阻塞等待,CPU大部分时间是在阻塞,该情况下应尽可能减少阻塞所带来的消耗,所以要使线程数尽可能多;
线程数 = CPU 核心数 * 2;