四、Java线程
1、创建线程的方式
(1)、继承Thread类
public class ThreadDemo extends Thread {}
①、Thread类常用构造方法
a、Thread():分配新的Thread对象
b、Thread(String name):分配新的Thread对象,将制定的name作为线程名
c、Thread(Runnable target, String name)
②、Thread类常用方法
a、public static native Thread currentThread():返回当前正在执行的线程对象的引用
b、public final String getName():返回该线程的名称
c、public synchronized void start():使线程开始执行,Java虚拟机调用该线程的run()方法,两次调用start()方法会报错,因为方法内部的threadStatus已经不为0,说明此线程不是新生的,已经被调用过了,报IllegalThreadStateException异常。
d、public void run():该线程要执行的操作,如:循环100次打印变量值
e、static native void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
f、public final void join():该方法的定义是等待该线程终止,将挂起调用线程的执行,直到被调用的对象完成它的执行。例:T1、T2、T3三个线程,保证T2在T1执行完后执行,T3在T2执行完后执行;在T2的run()中调用T1.join()可实现T2在T1执行完后执行。join方法的对象必须已经调用了start()方法且并未进入终止状态。
g、public static native void yield():
③、Object对象常用方法
a、public final native void wait(long timeout):当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
b、public final native void notify():唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
注:native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由Java去调用。这些函数的实现体在DLL中,JDK的源代码中并不包含该方法。
(2)、实现Runnable接口
public class RunnableDemo implements Runnable {}
(3)、实现Callable接口
public class CallableDemo implements Callable {}
(4)、wait()、notify()和notifyAll()实现原理
①、锁是每个对象的基础,每个对象都有锁。wait()、notify()和notifyAll()方法是Object类里面的。
②、JVM会为一个使用内部锁(synchronized)的对象维护两个集合,Entry Set和Wait Set,也有人翻译为锁池和等待池。
③、Entry Set:如果线程A已经持有了对象锁,此时如果有其他线程也想获得该对象锁的话,它只能进入Entry Set,并且处于线程的BLOCKED状态。
④、Wait Set:如果线程A调用了wait()方法,那么线程A会释放该对象的锁,进入到Wait Set,并且处于线程的WAITING状态。
⑤、某个线程B想要获得该对象锁,一是对象锁已经被释放了,二是线程B已处于RUNNABLE状态。
⑥、当对象的notify()方法被调用时,JVM会唤醒处于Wait Set中的某一个线程,这个线程的状态就从WAITING转变为RUNNABLE;或者当notifyAll()方法被调用时,Wait Set中的全部线程会转变为RUNNABLE状态。所有Wait Set中被唤醒的线程会被转移到Entry Set中。
⑦、然后,每当对象的锁被释放后,那些所有处于RUNNABLE状态的线程会共同去竞争获取对象的锁,最终会有一个线程(取决于JVM实现)真正获取到对象的锁,而其他竞争失败的线程继续在Entry Set中等待下一次机会。
注:必须从同步代码块内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。
2、区别比较
(1)、Runnable和Thread区别:
实现Runnable接口好处:
①、实现Runnable接口避免了Thread单继承的局限性。
②、实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。实现Runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
③、继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,又有线程任务。
(2)、Runnable接口和Callable接口区别:
相同点:
①、两者都是接口
②、两者都需要调用Thread.start启动线程
不同点:
①、Callable的核心是call方法,允许返回值,Runnable的核心是run方法,没有返回值
②、Callable接口的call()方法允许抛出异常;Runnable的run()方法异常只能在内部消化,不能往上继续抛
③、Runnable没有返回值,Callable可以返回执行结果,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞,通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果
④、加入线程池运行,Runnable使用ExecutorService的execute(无返回值)或submit(返回值为Future,Future.get()方法为null)方法,Callable使用submit(返回值为Future定义的泛型T)方法
(3)、sleep()和wait()的区别
①、sleep()方法是Thread类里面的,主要的意义就是让当前线程停止执行,让出cpu给其他的线程,但是不会释放对象锁资源以及监控的状态,当指定的时间到了之后又会自动恢复运行状态。
②、wait()方法是Object类里面的,主要的意义就是让线程放弃当前的对象的锁,进入等待此对象的等待锁定池,只有针对此对象调动notify方法后本线程才能够进入对象锁定池准备获取对象锁进入运行状态。
(4)、run()和start()区别
如果只是调用run()方法,仅是对象调用方法,不开启线程,代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。只有线程对象调用start()开启线程,才会表现出多线程的特性,让JVM调用run()方法在开启的线程中执行。
(5)、线程池的submit()和execute()的区别
submit底层调用的还是execute
①、参数不同
submit方法:
public Future<?> submit(Runnable task) {}
public <T> Future<T> submit(Runnable task, T result) {}
public <T> Future<T> submit(Callable<T> task) {}
execute方法:
public void execute(Runnable task) {}
public void execute(Runnable task, long startTimeout) {}
execute只能接受Runnable类型的任务;
submit不管是Runnable还是Callable类型的任务都可以接受,但是Runnable返回值均为void,所以使用Future的get()获得的还是null;
②、返回值不同:由Callable和Runnable的区别可知
execute没有返回值;
submit有返回值,所以需要返回值的时候必须使用submit;
③、异常处理不同:
execute中抛出异常:execute中的是Runnable接口的实现,所以只能使用try、catch来捕获CheckedException,当线程的执行过程中抛出了异常通常来说主线程也无法获取到异常的信息的,只有通过ThreadFactory主动设置线程的异常处理类才能感知到提交的线程中的异常信息。
submit中抛出异常:不管提交的是Runnable还是Callable类型的任务,如果不对返回值Future调用get()方法,都会吃掉异常。
execute直接抛出异常之后线程就死掉了,submit保存异常线程没有死掉。
3、线程状态
(1)、初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
(2)、运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
①、就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
②、运行中是指线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得cpu时间片后变为运行中状态(running)。
(3)、阻塞(BLOCKED):阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
(4)、等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
(5)、超时等待(TIME_WAITING):该状态不同于WAITING,它可以在指定的时间内自行返回。
(6)、终止(TERMINATED):当线程的run()方法完成时,或者主线程的main()方法完成时,就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
4、线程池
ThreadPoolTaskExecutor是spring core包中的,而ThreadPoolExecutor是JDK中的JUC。ThreadPoolTaskExecutor是对ThreadPoolExecutor进行了封装处理,ThreadPoolTaskExecutor的实现方式完全是使用ThreadPoolExecutor进行实现。
(1)、ThreadPoolExecutor构造方法及参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {...}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {...}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {...}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
①、corePoolSize(核心线程数):核心线程会一直存活,即使没有任务需要执行。当线程数小于核心线程数时(还未满,就会一直增),即使有线程空闲,线程池也会优先创建新线程处理。设置allowCoreThreadTimeout=true(默认false)时,核心线程超时会被销毁。
②、maximumPoolSize(线程池所容纳的最大线程数):当线程数>corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务,直到线程数量达到maxPoolSize;当线程数已经=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常。
③、keepAliveTime(线程闲置超时时长):当线程空闲时间达到keepAliveTime时,线程会被销毁,直到线程数量=corePoolSize;如果allowCoreThreadTimeout=true,则会直到线程数量=0(这个特性需要注意)
④、unit(指定参数keepAliveTime的时间单位):常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
⑤、workQueue(任务队列):当核心线程数达到最大时,新任务会放在队列中排队等待执行,其采用阻塞队列实现。
⑥、threadFactory(线程工厂):用于指定为线程池创建新线程的方式。设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程做些更有意义的事情,比如设置daemon和优先级等等。
⑦、handler(拒绝策略):当达到最大线程数时需要执行的饱和策略。
⑧、allowCoreThreadTimeOut(默认false):true—则线程池数量最后销毁到0个;false—超过核心线程数时,而且超过最大值或者timeout过,就会销毁。
(2)、线程池类型
java.util.concurrent.Executors类提供了大量创建连接池的静态方法,主要有以下四种。
①、newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
线程池的线程数量达corePoolSize后,即使线程池没有可执行任务时,也不会释放线程。FixedThreadPool的工作队列为无界队列LinkedBlockingQueue(队列容量为Integer.MAX_VALUE)。
这会有以下问题:线程池里的线程数量不超过corePoolSize,这导致了maximumPoolSize和keepAliveTime将会是个无用参数;由于使用了无界队列, 所以FixedThreadPool永远不会拒绝, 即饱和策略失效;newFixedThreadPool堆积的请求处理队列会消耗非常大的内存,可能引发OOM。
②、newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,最多会有一个任务处于活动状态。唯一的线程可以保证所提交任务的顺序执行,并且不予许使用者改动线程池实例,因此可以避免改变线程数目。由于使用了无界队列, 所以SingleThreadPool永远不会拒绝, 即饱和策略失效。newSingleThreadExecutor堆积的请求处理队列会消耗非常大的内存,可能引发OOM。
③、newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;和newFixedThreadPool创建的线程池不同,newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;
执行过程与前两种稍微不同:
a、主线程调用SynchronousQueue的offer()方法放入task,倘若此时线程池中有空闲的线程尝试读取SynchronousQueue的task,即调用了SynchronousQueue的poll(),那么主线程将该task交给空闲线程,否则执行(2)。
b、当线程池为空或者没有空闲的线程,则创建新的线程执行任务。
c、执行完任务的线程倘若在60s内仍空闲,则会被终止。因此长时间空闲的CachedThreadPool不会持有任何线程资源。
④、Executors和ThreadPoolExecutor区别:
Executors线程池的创建,底层的实现都是由ThreadPoolExecutor实现的,只是传了不同的参数。所以我们直接使用ThreadPoolExecutor类创建线程池,因为可以自定义传入我们设置的线程池的参数,更加灵活。
(3)、RejectedExecutionHandler拒绝策略:
两种情况会拒绝处理任务:
①、当线程数已经达到maxPoolSize,且任务队列已满时,会拒绝新任务。
②、当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务(并不是立马停止,而是执行完再停止)。若拒绝后,此时,线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置,默认值是AbortPolicy,会抛出异常。
ThreadPoolExecutor类有几个内部实现类来处理这类情况:
a、AbortPolicy:丢弃任务,抛运行时异常。
b、CallerRunsPolicy:执行任务(这个策略重试添加当前的任务,他会自动重复调用execute() 方法,直到成功) 如果执行器已关闭,则丢弃。
c、DiscardPolicy:对拒绝任务直接无声抛弃,没有异常信息。
d、DiscardOldestPolicy:对拒绝任务不抛弃,而是抛弃队列里面等待最久的(队列头部的任务将被删除)一个线程,然后把拒绝任务加到队列(Queue是先进先出的任务调度算法,具体策略会在下面有分析)(如果再次失败,则重复此过程)。
e、实现RejectedExecutionHandler接口,可自定义处理器(可以自己实现然后set进去)。
(4)、BlockingQueue<Runnable>任务队列:
①、ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
②、LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
③、SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
④、PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
(5)、线程池执行原理
①、当线程数小于核心线程数时,创建线程
②、当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列
③、当线程数大于等于核心线程数,且任务队列已满
④、若线程数小于最大线程数,创建线程
⑤、若线程数等于最大线程数,抛出异常,拒绝任务
(4)、如何合理配置线程池的maximumPoolSize
System.out.println(Runtime.getRuntime().availableProcessors());
通过availableProcessors可以拿到CPU核数,然后再进行后续判断。
SOB配置:corePoolSize:50;maxPoolSize:200;keepAliveTime:10;queueCapacity:200;
①、CPU密集型
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。CPU密集任务只有在真正的多核CPU上才能得到加速。CPU密集型任务配置尽可能少的线程数量。公式:CPU核数 + 1个线程
②、IO密集型
a、IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
IO密集型时,大部分线程都阻塞,故需要多配置线程数,参考公式:CPU核数/(1-阻塞系数) 阻塞系数在0.8~0.9之间。例8核CPU:8/(1-0.9) = 80个
b、由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2。
5、CompletableFuture原理
(1)、CompletableFuture是Java 8引入的新类,该类实现了Future接口和 CompletionStage接口。这个类主要的作用就是提供了新的方式来完成异步处理,包括合成和组合事件的非阻塞方式。管道式类,同lambda流stream管道式处理一样,需要终端方法终止。
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
...
}
CompletionStage接口代表异步计算中的不同阶段,以及如何组合这些计算阶段。该接口中有50多个方法,可以对CompletableStage进行组合、计算。按功能主要分为3类:
①、方法名不带Async的方法:同步方法。
②、方法名带Async但只有一个参数的方法:异步方法,使用默认的ForkJoinPool.commonPool()获取线程池。
③、方法名带Async但有两个参数的方法:异步方法,且使用第二个参数指定的ExecutorService线程池。
(2)、创建CompletableFuture对象
/**
* 比较特殊,他入参就是返回值,也就是说他可以用来执行需要其他返回值的异步任务。
*/
public static <U> CompletableFuture<U> completedFuture(U value) {
return new CompletableFuture<U>((value == null) ? NIL : value);
}
/**
* 无返回值,使用ForkJoinPool.commonPool()默认线程池
*/
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(asyncPool, runnable);
}
/**
* 无返回值,使用自定义线程池
*/
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
/**
* 有返回值,使用ForkJoinPool.commonPool()默认线程池
*/
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
/**
* 有返回值,使用自定义线程池
*/
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
①、Runnable:带run的方法参数,不带返回结果
②、Supplier:带supply的方法参数,带有返回结果。
③、Function:带apply的方法参数,带返回结果。
④、Consumer:带accept的方法参数,不带返回结果。
⑤、BiFunction: 处理三件事的方法参数,带返回结果。
(3)、做一件事
①、exceptionally:捕捉异常。
②、thenApply:做一件事,在其结果上处理,生成新的结果。
(4)、做两件事
①、thenAccept :第一件事完成后做第二件事情(接受第一件事结果),不返回结果。
②、thenRun:第一件事完成后做第二件事(两件事无关系)。
③、whenComplete:第一件事完成后做第二件事(接受第一件事结果,并捕捉第一件事产生的异常)。
④、thenCompose:第一件事完成后做第二件事情(接受第一件事结果),返回结果。
(5)、做三件事
①、thenAcceptBoth:两件事情完成后做第三件事情(接受两件事情的结果),返回结果。
②、runAfterBoth:两件事情完成后做第三件事请(与前两件事无关系),不返回结果。
③、thenCombine:两件事情完成后做第三件事请,返回结果。
6、ThreadLocal原理
(1)、概述
ThreadLocal顾名思义可以理解为线程本地变量,是线程局部变量,ThreadLocal将变量的各个副本值保存在各个线程Thread中,Thread对象实例采用ThreadLocalMap数据结构来存储副本值。每个线程往这个ThreadLocal中读写是线程隔离,也就是一个线程对资源的修改,不影响另一个线程的运行。在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。在进行get之前,必须先set,否则会报空指针异常。如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
(2)、源码解析
public class Thread implements Runnable {
//ThreadLocalMap为ThreadLocal对象的内部静态类
ThreadLocal.ThreadLocalMap threadLocals = null;
/**
* set值到ThreadLocalMap中
*/
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
ThreadLocal.ThreadLocalMap map = getMap(t);
//如果ThreadLocalMap不为空直接set值,如果为空则创建ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* 获取Thread中的ThreadLocalMap对象转换返回
*/
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
ThreadLocal.ThreadLocalMap map = getMap(t);
//如果ThreadLocalMap不为空则取值,如果为空则创建初始化ThreadLocalMap
if (map != null) {
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
/**
* 获取Thread中的ThreadLocalMap
*/
ThreadLocal.ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
}
(3)、适用场景
①、每个线程需要有自己单独的实例
②、实例需要在多个方法中共享,但不希望被多线程共享