一、什么是线程和进程?
进程:在操做系统中可以独立运行,而且做为资源分配的基本单位。它表示运行中的程序。系统运行一个程序就是一个进程从建立、运行到消亡的过程。html
线程:是一个比进程更小的执行单位,可以完成进程中的一个功能,也被称为轻量级进程。一个进程在其执行的过程当中能够产生多个线程。java
【注】线程与进程不一样的是:同类的多个线程共享进程的堆和方法区资源,但每一个线程有本身的程序计数器、虚拟机栈和本地方法栈,因此系统在产生一个线程,或是在各个线程之间做切换工做时,负担要比进程小得多。git
为何程序计数器、虚拟机栈和本地方法栈是线程私有的呢?为何堆和方法区是线程共享的呢?算法
程序计数器为何是私有的?数据库
程序计数器主要有下面两个做用:小程序
- 字节码解释器经过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
- 在多线程的状况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候可以知道该线程上次运行到哪儿了。
(须要注意的是,若是执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。)数组
因此,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。安全
- 虚拟机栈和本地方法栈为何是私有的?
- 虚拟机栈: 每一个 Java 方法在执行的同时会建立一个栈帧用于存储局部变量表、操做数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
- 本地方法栈: 和虚拟机栈所发挥的做用很是类似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
因此,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。服务器
堆和方法区是全部线程共享的资源,其中:多线程
- 堆是进程中最大的一块内存,主要用于存放新建立的对象 (全部对象都在这里分配内存)。
- 方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
二、什么是上下文切换?
即便单核处理器也支持多线程执行代码,CPU经过给每一个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,由于时间片很是短,因此CPU经过不停地切换线程执行,让咱们感受多个线程是同时执行的。(时间片通常是几十毫秒)
CPU经过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。可是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,能够再加载这个任务的状态。因此任务从保存到加载的过程就是一次上下文切换。上下文切换会影响多线程的执行速度。
三、并发与并行?
并发指的是多个任务交替进行,并行则是指真正意义上的“同时进行”。
实际上,若是系统内只有一个CPU,使用多线程时,在真实系统环境下不能并行,只能经过切换时间片的方式交替进行,从而并发执行任务。真正的并行只能出如今拥有多个CPU的系统中。
四、线程的生命周期和状态?(重要!)
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不一样状态的其中一个状态:
初始状态、运行状态、阻塞状态、等待状态、超时等待状态、终止状态
线程在生命周期中并非固定处于某一个状态而是随着代码的执行在不一样状态之间切换:
- 线程建立以后它将处于 初始状态(NEW),调用
start()
方法后开始运行,线程这时候处于 可运行状态(READY)。 - 可运行状态的线程得到了 CPU 时间片后就处于 运行状态(RUNNING)。
- 当线程执行
wait()
方法以后,线程进入 等待状态(WAITING),进入等待状态的线程须要依靠其余线程的通知才可以返回到运行状态【notify()】。 而 超时等待状态(TIME_WAITING)至关于在等待状态的基础上增长了超时限制,【sleep(long millis)/
wait(long millis)】,
当超时时间到达后 Java 线程将会返回到运行状态。 - 当线程调用同步方法时,在没有获取到锁的状况下,线程将会进入到阻塞状态(BLOCKED)。
- 线程在执行 Runnable 的
run()
方法以后将会进入到 终止状态(TERMINATED)。
五、什么是线程死锁?如何避免死锁?
多个线程同时被阻塞,它们中的一个或者所有都在等待某个资源被释放。因为线程被无限期地阻塞,所以程序不可能正常终止。
假如线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,因此这两个线程就会互相等待而进入死锁状态。
避免死锁的几个常见方法:
- 避免一个线程同时获取多个锁
- 避免一个线程在锁内同时占用多个资源,尽可能保证每一个锁只占用一个资源。
- 尝试使用定时锁,使用 lock.tryLock(timeout) 来代替使用内部锁机制。
- 对于数据库锁,加锁和解锁必须在一个数据库链接里,不然会出现解锁失败的状况。
六、sleep() 方法和 wait() 方法区别和共同点?(重要!)
相同点:
二者均可以暂停线程的执行,都会让线程进入等待状态。
不一样点:
- sleep()方法没有释放锁,而 wait()方法释放了锁。
- sleep()方法属于Thread类的静态方法,做用于当前线程;而wait()方法是Object类的实例方法,做用于对象自己。
- 执行sleep()方法后,能够经过超时或者调用interrupt()方法唤醒休眠中的线程;执行wait()方法后,经过调用notify()或notifyAll()方法唤醒等待线程。
七、为何咱们调用 start() 方法时会执行 run() 方法,为何咱们不能直接调用 run() 方法?
new 一个 Thread,线程进入初始状态;调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就能够开始运行了。 start() 会执行线程的相应准备工做,而后自动执行 run() 方法的内容,这是真正的多线程工做。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,因此这并非多线程工做。
总结: 调用 start 方法可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,仍是在主线程里执行。
八、多线程开发带来的问题与解决方法?(重要)
使用多线程主要会带来如下几个问题:
(一)线程安全问题
线程安全问题指的是在某一线程从开始访问到结束访问某一数据期间,该数据被其余的线程所修改,那么对于当前线程而言,该线程就发生了线程安全问题,表现形式为数据的缺失,数据不一致等。
线程安全问题发生的条件:
1)多线程环境下,即存在包括本身在内存在有多个线程。
2)多线程环境下存在共享资源,且多线程操做该共享资源。
3)多个线程必须对该共享资源有非原子性操做。
线程安全问题的解决思路:
1)尽可能不使用共享变量,将没必要要的共享变量变成局部变量来使用。
2)使用synchronized关键字同步代码块,或者使用jdk包中提供的Lock为操做进行加锁。
3)使用ThreadLocal为每个线程创建一个变量的副本,各个线程间独立操做,互不影响。
(二)性能问题
线程的生命周期开销是很是大的,一个线程的建立到销毁都会占用大量的内存。同时若是不合理的建立了多个线程,cup的处理器数量小于了线程数量,那么将会有不少的线程被闲置,闲置的线程将会占用大量的内存,为垃圾回收带来很大压力,同时cup在分配线程时还会消耗其性能。
解决思路:
利用线程池,模拟一个池,预先建立有限合理个数的线程放入池中,当须要执行任务时从池中取出空闲的先去执行任务,执行完成后将线程归还到池中,这样就减小了线程的频繁建立和销毁,节省内存开销和减少了垃圾回收的压力。同时由于任务到来时自己线程已经存在,减小了建立线程时间,提升了执行效率,并且合理的建立线程池数量还会使各个线程都处于忙碌状态,提升任务执行效率,线程池还提供了拒绝策略,当任务数量到达某一临界区时,线程池将拒绝任务的进入,保持现有任务的顺利执行,减小池的压力。
(三)活跃性问题
1)死锁,假如线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,因此这两个线程就会互相等待而进入死锁状态。多个线程环形占用资源也是同样的会产生死锁问题。
解决方法:
- 避免一个线程同时获取多个锁
- 避免一个线程在锁内同时占用多个资源,尽可能保证每一个锁只占用一个资源。
- 尝试使用定时锁,使用 lock.tryLock(timeout) 来代替使用内部锁机制。
想要避免死锁,能够使用无锁函数(cas)或者使用重入锁(ReentrantLock),经过重入锁使线程中断或限时等待能够有效的规避死锁问题。
2)饥饿,饥饿指的是某一线程或多个线程由于某些缘由一直获取不到资源,致使程序一直没法执行。如某一线程优先级过低致使一直分配不到资源,或者是某一线程一直占着某种资源不放,致使该线程没法执行等。
解决方法:
与死锁相比,饥饿现象仍是有可能在一段时间以后恢复执行的。能够设置合适的线程优先级来尽可能避免饥饿的产生。
3)活锁,活锁体现了一种谦让的美德,每一个线程都想把资源让给对方,可是因为机器“智商”不够,可能会产生一直将资源让来让去,致使资源在两个线程间跳动而没法使某一线程真正的到资源并执行,这就是活锁的问题。
(四)阻塞
阻塞是用来形容多线程的问题,几个线程之间共享临界区资源,那么当一个线程占用了临界区资源后,全部须要使用该资源的线程都须要进入该临界区等待,等待会致使线程挂起,一直不能工做,这种状况就是阻塞,若是某一线程一直都不释放资源,将会致使其余全部等待在这个临界区的线程都不能工做。当咱们使用synchronized或重入锁时,咱们获得的就是阻塞线程,如论是synchronized或者重入锁,都会在试图执行代码前,获得临界区的锁,若是得不到锁,线程将会被挂起等待,知道其余线程执行完成并释放锁且拿到锁为止。
解决方法:
能够经过减小锁持有时间,读写锁分离,减少锁的粒度,锁分离,锁粗化等方式来优化锁的性能。
临界区:
临界区是用来表示一种公共的资源(共享数据),它能够被多个线程使用,可是在每次只能有一个线程可以使用它,当临界区资源正在被一个线程使用时,其余的线程就只能等待当前线程执行完以后才能使用该临界区资源。
好比办公室办公室里有一支笔,它一次只能被一我的使用,假如它正在被甲使用时,其余想要使用这支笔的人只能等甲使用完这支笔以后才能容许另外一我的去使用。这就是临界区的概念。
参考 http://www.javashuo.com/article/p-vczuwcuu-gs.html
九、 synchronized 关键字
synchronized关键字能够保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
synchronized关键字最主要的三种使用方式:修饰实例方法:、修饰静态方法、修饰代码块。
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的Class对象。
- 对于同步代码块,锁是synchronized括号里配置的对象。
当一个线程试图访问同步代码块时,它首先必须获得锁,退出或抛出异常时必须释放锁。
synchronized在JVM里是怎么实现的?
synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor的持有权。当计数器为0则能够成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,代表锁被释放。若是获取对象锁失败,那当前线程就要阻塞等待,直到锁被另一个线程释放为止。
(monitor对象存在于每一个Java对象的对象头中,synchronized 锁即是经过这种方式获取锁的,也是为何Java中任意对象能够做为锁的缘由)
synchronized用的锁是存在哪里的?
synchronized用到的锁是存在Java对象头里的。
十、说说 JDK1.6 以后的synchronized 关键字底层作了哪些优化,能够详细介绍一下这些优化吗
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减小锁操做的开销。
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁能够升级不可降级,这种策略是为了提升得到锁和释放锁的效率。
关于这几种优化的详细信息能够查看这篇文章:https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md
十一、synchronized和 Lock 的区别?(重要)
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,所以不会致使死锁现象发生;而Lock在发生异常时,若是没有主动经过unLock()去释放锁,则极可能形成死锁现象,所以使用Lock时须要在finally块中释放锁;
3)Lock可让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不可以响应中断;
4)经过Lock能够知道有没有成功获取锁(tryLock()方法:若是获取锁成功,则返回true),而synchronized却没法办到。
5)Lock能够提升多个线程进行读操做的效率。
在性能上来讲,若是竞争资源不激烈,二者的性能是差很少的,而当竞争资源很是激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。因此说,在具体使用时要根据适当状况选择。
参考https://blog.csdn.net/qq_38200548/article/details/82943222
十二、synchronized和ReentrantLock(重入锁) 的区别?
- 二者都是可重进入锁,就是可以支持一个线程对资源的重复加锁。sychnronized关键字隐式的支持重进入,好比一个sychnronized修饰的递归方法,在方法执行时,执行线程在获取了锁以后仍能连续屡次地获取该锁。ReentrantLock虽然没能像sychnronized关键字同样隐式的重进入,可是在调用lock()方法时,已经获取到锁的线程,可以再次调用lock()方法获取锁而不被阻塞。
- 线程重复n次获取了锁,随后在第n次释放该锁后,其余线程可以获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功被释放。
- synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,须要 lock() 和 unlock() 方法配合 try/finally 语句块来完成)
- ReentrantLock 比 synchronized 增长了一些高级功能,主要有3点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁能够绑定多个条件)
- ReentrantLock提供了一种可以中断等待锁的线程的机制,也就是说正在等待的线程能够选择放弃等待,改成处理其余事情。经过lock.lockInterruptibly()来实现这个机制。
- ReentrantLock能够指定是公平锁仍是非公平锁。而synchronized只能是非公平锁。(公平锁就是先等待的线程先得到锁)
- synchronized关键字与wait()和notify()/notifyAll()方法相结合能够实现等待/通知机制。ReentrantLock类固然也能够实现,可是须要借助于Condition接口与newCondition() 方法。用ReentrantLock类结合Condition实例能够实现“选择性通知” 。若是执行notifyAll()方法的话就会通知全部处于等待状态的线程这样会形成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的全部等待线程
1三、volatile关键字
保证共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。
把变量声明为volatile,这就指示 JVM每次使用它都到主存中进行读取。
1四、synchronized 关键字和 volatile 关键字的区别
- volatile关键字是线程同步的轻量级实现,因此volatile性能比synchronized关键字要好。可是volatile关键字只能用于变量而synchronized关键字能够修饰方法以及代码块。
- 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞。、
- volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。
- volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字二者都能保证。
1五、使用线程池的好处?
- 下降资源消耗。经过重复利用已建立的线程,下降线程建立和销毁形成的消耗。
- 提升响应速度。当任务到达时,任务能够不须要等到线程建立就能当即执行。
- 提升线程的可管理性。线程是稀缺资源,若是无限制地建立,不只会消耗系统资源,还会下降系统的稳定性,使用线程池能够进行统一分配、调优和监控。
1六、说一说几种常见的线程池及适用场景?(重要)
能够建立(Executors.newXXX)3种类型的ThreadPoolExecutor:FixedThreadPool、SingleThreadExecutor、CachedThreadPool。
- FixedThreadPool:可重用固定线程数的线程池。(适用于负载比较重的服务器)
- FixedThreadPool使用无界队列LinkedBlockingQueue做为线程池的工做队列
- 该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中如有空闲线程,则当即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
- SingleThreadExecutor:只会建立一个线程执行任务。(适用于须要保证顺序执行各个任务;而且在任意时间点,没有多线程活动的场景。)
- SingleThreadExecutorl也使用无界队列LinkedBlockingQueue做为工做队列
- 若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
- CachedThreadPool:是一个会根据须要调整线程数量的线程池。(大小无界,适用于执行不少的短时间异步任务的小程序,或负载较轻的服务器)
- CachedThreadPool使用没有容量的SynchronousQueue做为线程池的工做队列,但CachedThreadPool的maximumPool是无界的。
- 线程池的线程数量不肯定,但如有空闲线程能够复用,则会优先使用可复用的线程。若全部线程均在工做,又有新的任务提交,则会建立新的线程处理任务。全部线程在当前任务执行完毕后,将返回线程池进行复用。
- ScheduledThreadPool:继承自ThreadPoolExecutor。它主要用来在给定的延迟以后运行任务,或者按期执行任务。使用DelayQueue做为任务队列。
1六、线程池都有哪几种工做队列?(重要)
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量一般要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue:是一个不存储元素的阻塞队列。每一个插入操做必须等到另外一个线程调用移除操做,不然插入操做一直处于阻塞状态,吞吐量一般要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
- PriorityBlockingQueue:一个具备优先级的无限阻塞队列。
1七、建立线程的几种方式?(重要)
有4种方式:继承Thread类、实现Runnable接口、实现Callable接口、使用Executor框架来建立线程池。
(1)经过继承Thread类建立线程
public class MyThread extends Thread {//继承Thread类
//重写run方法
public void run(){
}
}
----------------------------------------------------------------------------------
public class Main {
public static void main(String[] args){
new MyThread().start(); //建立并启动线程
}
}
(2)经过实现Runnable接口来建立线程
public class MyThread2 implements Runnable {//实现Runnable接口
//重写run方法
public void run(){
}
}
------------------------------------------------------------------------------------------
public class Main {
public static void main(String[] args){
//建立并启动线程
MyThread2 myThread=new MyThread2();
Thread thread=new Thread(myThread);
thread().start();
//或者 new Thread(new MyThread2()).start();
}
}
无论是继承Thread仍是实现Runnable接口,多线程代码都是经过运行Thread的start()方法来运行的。
(3)实现Callable接口来建立线程
与实现Runnable接口相似,和Runnable接口不一样的是,Callable接口提供了一个call() 方法做为线程执行体,call()方法比run()方法功能要强大:call()方法能够有返回值、call()方法能够声明抛出异常。
public class Main {
public static void main(String[] args){
MyThread3 th=new MyThread3();
//使用Lambda表达式建立Callable对象
//使用FutureTask类来包装Callable对象
FutureTask<Integer> future=new FutureTask<Integer>(
(Callable<Integer>)()->{
return 5;
}
);
new Thread(task,"有返回值的线程").start();//实质上仍是以Callable对象来建立并启动线程
try{
System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回
}catch(Exception e){
ex.printStackTrace();
}
}
}
(4)使用Executor框架来建立线程池
Executors.newXXXX: newFixedThreadPool(int )、newSingleThreadExecutor、newCachedThreadPool、newScheduledThreadPool(int)
经过Executors的以上四个静态工厂方法得到 ExecutorService实例,然后能够执行Runnable任务或Callable任务。
- Executor执行Runnable任务:
经过Executors的以上四个静态工厂方法得到 ExecutorService实例,然后调用该实例的execute(Runnable command)方法便可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上。
public class TestCachedThreadPool{ public static void main(String[] args){ ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++){ executorService.execute(new TestRunnable()); System.out.println("************* a" + i + " *************"); } executorService.shutdown(); } } class TestRunnable implements Runnable{
//重写run方法 public void run(){ System.out.println(Thread.currentThread().getName() + "线程被调用了。"); }
- Executor执行Callable任务:
当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,而且会返回执行结果Future对象。一样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,而且会返回执行结果Future对象,可是在该Future对象上调用get方法,将返回null。
public class CallableDemo{ public static void main(String[] args){ ExecutorService executorService = Executors.newCachedThreadPool(); List<Future<String>> resultList = new ArrayList<Future<String>>(); //建立10个任务并执行 for (int i = 0; i < 10; i++){ //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中 Future<String> future = executorService.submit(new TaskWithResult(i)); //将任务执行结果存储到List中 resultList.add(future); } //遍历任务的结果 for (Future<String> fs : resultList){ try{ while(!fs.isDone);//Future返回若是没有完成,则一直循环等待,直到Future返回完成 System.out.println(fs.get()); //打印各个线程(任务)执行的结果 }catch(InterruptedException e){ e.printStackTrace(); }catch(ExecutionException e){ e.printStackTrace(); }finally{ //启动一次顺序关闭,执行之前提交的任务,但不接受新任务 executorService.shutdown(); } } } } class TaskWithResult implements Callable<String>{ private int id; public TaskWithResult(int id){ this.id = id; } // 重写call()方法 public String call() throws Exception { System.out.println("call()方法被自动调用!!! " + Thread.currentThread().getName()); //该返回结果将被Future的get方法获得 return "call()方法被自动调用,任务返回的结果是:" + id + " " + Thread.currentThread().getName(); } }
实现Runnable接口和Callable接口的区别?
Runnable接口或Callable接口实现类均可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。二者的区别在于 Runnable 接口不会返回结果可是 Callable 接口能够返回结果。
执行execute()方法和submit()方法的区别是什么呢?
1) execute()
方法用于提交不须要返回值的任务,因此没法判断任务是否被线程池执行成功与否;
2) submit()
方法用于提交须要返回值的任务。线程池会返回一个Future类型的对象,经过这个Future对象能够判断任务是否执行成功,而且能够经过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)
方法则会阻塞当前线程一段时间后当即返回,这时候有可能任务没有执行完。
18.线程池参数?
①corePoolSize:线程池的基本大小,当提交一个任务到线程池时,线程池会建立一个线程来执行任务,即便其余空闲的基本线程可以执行新任务也会建立线程,等到须要执行的任务数大于线程池基本大小时就再也不建立。说白了就是,即使是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。
②maximumPoolSize:最大线程数,无论你提交多少任务,线程池里最多工做线程数就是maximumPoolSize。
③keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,若是等了keepAliveTime时长尚未任务可执行,则线程退出。
⑤unit:这个用来指定keepAliveTime的单位,好比秒:TimeUnit.SECONDS。
⑥workQueue:用于保存等待执行任务的阻塞队列,提交的任务将会被放到这个队列里。
⑦threadFactory:线程工厂,用来建立线程。主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。
⑧handler:拒绝策略,即当线程和队列都已经满了的时候,应该采起什么样的策略来处理新提交的任务。默认策略是AbortPolicy(抛出异常),其余的策略还有:CallerRunsPolicy(只用调用者所在线程来运行任务)、DiscardOldestPolicy(丢弃队列里最近的一个任务,并执行当前任务)、DiscardPolicy(不处理,丢弃掉)
19.线程池执行流程?
任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,若是小于则建立线程来执行提交的任务,不然将任务放入workQueue队列,若是workQueue满了,则判断当前线程数量是否小于maximumPoolSize,若是小于则建立线程执行任务,不然就会调用handler,以表示线程池拒绝接收任务。