本篇文章属于干货内容!请各位读者朋友必定要坚持读到最后,完整阅读本文后相信你对多线程会有不同感悟,下次面试和面试官也能杠一杠相关内容了。java
进程是系统中正在运行的一个程序,程序一旦运行就是进程。进程能够当作程序执行的一个实例。进程是系统资源分配的独立实体,每一个进程都拥有独立的地址空间。一个进程没法访问另外一个进程的变量和数据结构,若是想让一个进程访问另外一个进程的资源,须要使用进程间通讯,好比管道,文件,套接字等。面试
是操做系统可以进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运做单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中能够并发多个线程,每条线程并行执行不一样的任务。算法
1.继承Thread类2.实现Runnable接口3.使用Callable和Future数组
1.start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,能够直接继续执行下面的代码;经过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并无运行。而后经过此Thread类调用方法run()来完成其运行操做的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。而后CPU再调度其它线程。安全
2.run()方法看成普通方法的方式调用。程序仍是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码;程序中只有主线程------这一个线程, 其程序执行路径仍是只有一条, 这样就没有达到写线程的目的。微信
new建立一个Thread对象时,并没处于执行状态,由于没有调用start方法启动改线程,那么此时的状态就是新建状态。网络
线程对象经过start方法进入runnable状态,启动的线程不必定会当即获得执行,线程的运行与否要看cpu的调度,咱们把这个中间状态叫可执行状态(RUNNABLE)。数据结构
一旦cpu经过轮询货其余方式从任务能够执行队列中选中了线程,此时它才能真正的执行本身的逻辑代码。多线程
线程正在等待获取锁。并发
TERMINATED是一个线程的最终状态,在该状态下线程不会再切换到其余任何状态了,表明整个生命周期都结束了。下面几种状况会进入TERMINATED状态:
示例代码:
public class XkThread extends Thread { private int i = 5; @Override public void run() { System.out.println("i=" + (i------------------) + " threadName=" + Thread.currentThread().getName()); } public static void main(String[] args) { XkThread xk = new XkThread(); Thread t1 = new Thread(xk); Thread t2 = new Thread(xk); Thread t3 = new Thread(xk); Thread t4 = new Thread(xk); Thread t5 = new Thread(xk); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); }}
结果:
i=5 threadName=Thread-1i=2 threadName=Thread-5i=5 threadName=Thread-2i=4 threadName=Thread-3i=3 threadName=Thread-4
虽然println()方法在内部是同步的,但i------------------的操做倒是在进入println()以前发生的,因此有发生非线程安全的几率。
println()源码:
public void println(String x) { synchronized (this) { print(x); newLine(); } }
12.如何知道代码段被哪一个线程调用?
System.out.println(Thread.currentThread().getName());
13.线程活动状态?
public class XKThread extends Thread { @Override public void run() { System.out.println("run run run is " + this.isAlive() ); } public static void main(String[] args) { XKThread xk = new XKThread(); System.out.println("begin --------- " + xk.isAlive()); xk.start(); System.out.println("end --------------- " + xk.isAlive()); }}
方法sleep()的做用是在指定的毫秒数内让当前的"正在执行的线程"休眠(暂停执行)。
jdk1.5 后,引入了一个枚举TimeUnit,对sleep方法提供了很好的封装。好比要表达2小时22分55秒899毫秒。
Thread.sleep(8575899L);TimeUnit.HOURS.sleep(3);TimeUnit.MINUTES.sleep(22);TimeUnit.SECONDS.sleep(55);TimeUnit.MILLISECONDS.sleep(899);
能够看到表达的含义更清晰,更优雅。线程休眠只会用 Thread.sleep?来,教你新姿式!推荐看下。
run方法执行完成,天然终止。stop()方法,suspend()以及resume()都是过时做废方法,使用它们结果不可预期。大多数中止一个线程的操做使用Thread.interrupt()等于说给线程打一个中止的标记, 此方法不回去终止一个正在运行的线程,须要加入一个判断才能能够完成线程的中止。
interrupted : 判断当前线程是否已经中断,会清除状态。isInterrupted :判断线程是否已经中断,不会清除状态。
放弃当前cpu资源,将它让给其余的任务占用cpu执行时间。但放弃的时间不肯定,有可能刚刚放弃,立刻又得到cpu时间片。
测试代码:(cpu独占时间片)
public class XKThread extends Thread { @Override public void run() { long beginTime = System.currentTimeMillis(); int count = 0; for (int i = 0; i < 50000000; i++) { count = count + (i + 1); } long endTime = System.currentTimeMillis(); System.out.println("用时 = " + (endTime - beginTime) + " 毫秒! "); } public static void main(String[] args) { XKThread xkThread = new XKThread(); xkThread.start(); }}
结果:
用时 = 20 毫秒!
加入yield,再来测试。(cpu让给其余资源致使速度变慢)
public class XKThread extends Thread { @Override public void run() { long beginTime = System.currentTimeMillis(); int count = 0; for (int i = 0; i < 50000000; i++) { Thread.yield(); count = count + (i + 1); } long endTime = System.currentTimeMillis(); System.out.println("用时 = " + (endTime - beginTime) + " 毫秒! "); } public static void main(String[] args) { XKThread xkThread = new XKThread(); xkThread.start(); }}
结果:
用时 = 38424 毫秒!
在操做系统中,线程能够划分优先级,优先级较高的线程获得cpu资源比较多,也就是cpu有限执行优先级较高的线程对象中的任务,可是不能保证必定优先级高,就先执行。Java的优先级分为1~10个等级,数字越大优先级越高,默认优先级大小为5。超出范围则抛出:java.lang.IllegalArgumentException。
线程的优先级具备继承性,好比a线程启动b线程,b线程与a优先级是同样的。
设置优先级高低两个线程,累加数字,看谁跑的快,上代码。
public class Run extends Thread{ public static void main(String[] args) { try { ThreadLow low = new ThreadLow(); low.setPriority(2); low.start(); ThreadHigh high = new ThreadHigh(); high.setPriority(8); high.start(); Thread.sleep(2000); low.stop(); high.stop(); System.out.println("low = " + low.getCount()); System.out.println("high = " + high.getCount()); } catch (InterruptedException e) { e.printStackTrace(); } }}class ThreadHigh extends Thread { private int count = 0; public int getCount() { return count; } @Override public void run() { while (true) { count++; } }}class ThreadLow extends Thread { private int count = 0; public int getCount() { return count; } @Override public void run() { while (true) { count++; } }}
结果:
low = 1193854568high = 1204372373
Java线程有两种,一种是用户线程,一种是守护线程。
守护线程是一个比较特殊的线程,主要被用作程序中后台调度以及支持性工做。当Java虚拟机中不存在非守护线程时,守护线程才会随着JVM一同结束工做。
GC(垃圾回收器)
Thread.setDaemon(true)PS:Daemon属性须要再启动线程以前设置,不能再启动后设置。
Java虚拟机退出时Daemon线程中的finally块并不必定会执行。代码示例:
public class XKDaemon { public static void main(String[] args) { Thread thread = new Thread(new DaemonRunner(),"xkDaemonRunner"); thread.setDaemon(true); thread.start(); } static class DaemonRunner implements Runnable { @Override public void run() { try { SleepUtils.sleep(10); } finally { System.out.println("Java小咖秀 daemonThread finally run ..."); } } }}
结果:没有任何的输出,说明没有执行finally。
获取线程上下文类加载器
public ClassLoader getContextClassLoader()
设置线程类加载器(能够打破Java类加载器的父类委托机制)
public void setContextClassLoader(ClassLoader cl)
join是指把指定的线程加入到当前线程,好比join某个线程a,会让当前线程b进入等待,直到a的生命周期结束,此期间b线程是处于blocked状态。
synchronized关键字能够时间一个简单的策略来防止线程干扰和内存一致性错误,若是一个对象是对多个线程可见的,那么对该对想的全部读写都将经过同步的方式来进行。
monitor enter 和 monitor exit
能够用于对代码块或方法的修饰,
普通同步方法 ---------------> 锁的是当前实力对象。静态同步方法---------------> 锁的是当前类的Class对象。同步方法快 ---------------> 锁的是synchonized括号里配置的对象。
synchronized用的锁是存在Java对象头里的。对象若是是数组类型,虚拟机用3个字宽(Word)存储对象头,若是对象是非数组类型,用2字宽存储对象头。Tips:32位虚拟机中一个字宽等于4字节。
32位JVM的Mark Word 默认存储结构
Mark Word 存储的数据会随着锁标志为的变化而变化。
64位虚拟机下,Mark Word是64bit大小的
Java SE 1.6 为了提升锁的性能。引入了"偏向锁"和轻量级锁"。Java SE 1.6 中锁有4种状态。级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。锁只能升级不能降级。
大多数状况,锁不只不存在多线程竞争,并且总由同一线程屡次得到。当一个线程访问同步块并获取锁时,会在对象头和栈帧中记录存储锁偏向的线程ID,之后该线程在进入和退出同步块时不须要进行 cas操做来加锁和解锁,只需测试一下对象头 Mark Word里是否存储着指向当前线程的偏向锁。若是测试成功,表示线程已经得到了锁,若是失败,则须要测试下Mark Word中偏向锁的标示是否已经设置成1(表示当前时偏向锁),若是没有设置,则使用cas竞争锁,若是设置了,则尝试使用cas将对象头的偏向锁只想当前线程。
java6和7中默认启用,可是会在程序启动几秒后才激活,若是须要关闭延迟,-XX:BiasedLockingStartupDelay=0。
JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。Tips:若是你能够肯定程序的全部锁一般状况处于竞态,则能够选择关闭。
线程在执行同步块,jvm会如今当前线程的栈帧中建立用于储存锁记录的空间。并将对象头中的Mark Word复制到锁记录中。而后线程尝试使用cas将对象头中的Mark Word替换为之乡锁记录的指针。若是成功,当前线程得到锁,若是失败,表示其余线程竞争锁,当前线程便尝试使用自旋来获取锁。
轻量锁解锁时,会使原子操做cas将 displaced Mark Word 替换回对象头,若是成功则表示没有竞争发生,若是失败,表示存在竞争,此时锁就会膨胀为重量级锁。
不可被中断的一个或一系列操做
Java中经过锁和循环cas的方式来实现原子操做,JVM的CAS操做利用了处理器提供的CMPXCHG指令来实现的。自旋CAS实现的基本思路就是循环进行CAS操做直到成功为止。另外,关注微信公众号:Java技术栈,在后台回复:java,能够获取我整理的 N 篇 Java 及多线程干货。
ABA问题,循环时间长消耗资源大,只能保证一个共享变量的原子操做
问题:由于cas须要在操做值的时候,检查值有没有变化,若是没有变化则更新,若是一个值原来是A,变成了B,又变成了A,那么使用cas进行检测时会发现发的值没有发生变化,实际上是变过的。解决:添加版本号,每次更新的时候追加版本号,A-B-A ---> 1A-2B-3A。从jdk1.5开始,Atomic包提供了一个类AtomicStampedReference来解决ABA的问题。
若是jvm能支持处理器提供的pause指令,那么效率会有必定的提高。1、它能够延迟流水线执行指令(de-pipeline),使cpu不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,有些处理器延迟时间是0。2、它能够避免在退出循环的时候因内存顺序冲突而引发的cpu流水线被清空,从而提升cpu执行效率。
1、对多个共享变量操做时,能够用锁。2、能够把多个共享变量合并成一个共享变量来操做。好比,x=1,k=a,合并xk=1a,而后用cas操做xk。Tips:java 1.5开始,jdk提供了AtomicReference类来保证饮用对象之间的原子性,就能够把多个变量放在一个对象来进行cas操做。
volatile 是轻量级的synchronized,它在多处理器开发中保证了共享变量的"可见性"。Java语言规范第3版对volatile定义以下,Java容许线程访问共享变量,为了保证共享变量能准确和一致的更新,线程应该确保排它锁单独得到这个变量。若是一个字段被声明为volatile,Java线程内存模型全部线程看到这个变量的值是一致的。
一个线程修改了一个对象的值,而另外一个线程感知到了变化,而后进行相应的操做。
方法wait()的做用是使当前执行代码的线程进行等待,wait()是Object类通用的方法,该方法用来将当前线程置入"预执行队列"中,并在 wait()所在的代码处中止执行,直到接到通知或中断为止。在调用wait以前线程须要得到该对象的对象级别的锁。代码体现上,即只能是同步方法或同步代码块内。调用wait()后当前线程释放锁。
notify()也是Object类的通用方法,也要在同步方法或同步代码块内调用,该方法用来通知哪些可能灯光该对象的对象锁的其余线程,若是有多个线程等待,则随机挑选出其中一个呈wait状态的线程,对其发出 通知 notify,并让它等待获取该对象的对象锁。
notify等于说将等待队列中的一个线程移动到同步队列中,而notifyAll是将等待队列中的全部线程所有移动到同步队列中。
等待
synchronized(obj) { while(条件不知足) { obj.wait(); } 执行对应逻辑}
通知
synchronized(obj) { 改变条件 obj.notifyAll();}
主要解决每个线程想绑定本身的值,存放线程的私有数据。
获取当前的线程的值经过get(),设置set(T) 方式来设置值。
public class XKThreadLocal { public static ThreadLocal threadLocal = new ThreadLocal(); public static void main(String[] args) { if (threadLocal.get() == null) { System.out.println("未设置过值"); threadLocal.set("Java小咖秀"); } System.out.println(threadLocal.get()); }}
输出:
未设置过值Java小咖秀
Tips:默认值为null
经过继承重写initialValue()方法便可。代码实现:
public class ThreadLocalExt extends ThreadLocal{ static ThreadLocalExt threadLocalExt = new ThreadLocalExt(); @Override protected Object initialValue() { return "Java小咖秀"; } public static void main(String[] args) { System.out.println(threadLocalExt.get()); }}
输出结果:
Java小咖秀
锁能够防止多个线程同时共享资源。Java5前程序是靠synchronized实现锁功能。Java5以后,并发包新增Lock接口来实现锁功能。
支持重进入的锁,它表示该锁可以支持一个线程对资源的重复加锁。除此以外,该锁的还支持获取锁时的公平和非公平性选择。详细阅读:到底什么是重入锁,拜托,一次搞清楚!另外,关注微信公众号:Java技术栈,在后台回复:面试,能够获取我整理的 N 篇 Java 面试题干货。
重进入是指任意线程在获取到锁以后可以再次获锁而不被锁阻塞。该特性主要解决如下两个问题:1、锁须要去识别获取锁的线程是否为当前占据锁的线程,若是是则再次成功获取。2、所得最终释放。线程重复n次是获取了锁,随后在第n次释放该锁后,其余线程可以获取到该锁。
默认非公平锁代码为证:
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
公平性与否针对获取锁来讲的,若是一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO。
Java中提供读写锁的实现类是ReentrantReadWriteLock。
定义了一组公共静态方法,提供了最基本的线程阻塞和唤醒功能。
提供了相似Object监视器方法,与 Lock配合使用实现等待/通知模式。
代码示例:
public class XKCondition { Lock lock = new ReentrantLock(); Condition cd = lock.newCondition(); public void await() throws InterruptedException { lock.lock(); try { cd.await();//至关于Object 方法中的wait() } finally { lock.unlock(); } } public void signal() { lock.lock(); try { cd.signal(); //至关于Object 方法中的notify() } finally { lock.unlock(); } }}
一个由数据支持的有界阻塞队列,此队列FIFO原则对元素进行排序。队列头部在队列中存在的时间最长,队列尾部存在时间最短。
一个支持优先级排序的***阻塞队列,但它不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。
是一个支持延时获取元素的使用优先级队列的实现的***阻塞队列。队列中的元素必须实现Delayed接口和 Comparable接口,在建立元素时能够指定多久才能从队列中获取当前元素。
ConcurrentHashMap、CopyOnWriteArrayList 、CopyOnWriteArraySet 、ConcurrentLinkedQueue、 ConcurrentLinkedDeque、ConcurrentSkipListMap、ConcurrentSkipListSet、ArrayBlockingQueue、 LinkedBlockingQueue、LinkedBlockingDeque、PriorityBlockingQueue、SynchronousQueue、 LinkedTransferQueue、DelayQueue
并发安全版HashMap,java7中采用分段锁技术来提升并发效率,默认分16段。Java8放弃了分段锁,采用CAS,同时当哈希冲突时,当链表的长度到8时,会转化成红黑树。(如需了解细节,见jdk中代码)
基于连接节点的***线程安全队列,它采用先进先出的规则对节点进行排序,当咱们添加一个元素的时候,它会添加到队列的尾部,当咱们获取一个元素时,它会返回队列头部的元素。它采用cas算法来实现。(如需了解细节,见jdk中代码)
阻塞队列是一个支持两个附加操做的队列,这两个附加操做支持阻塞的插入和移除方法。一、支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满。二、支持阻塞的移除方法:当队列空时,获取元素的线程会等待队列变为非空。
经常使用于生产者和消费者场景,生产者是往队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列正好是生产者存放、消费者来获取的容器。
ArrayBlockingQueue:数组结构组成的 |有界阻塞队列LinkedBlockingQueue:链表结构组成的|有界阻塞队列PriorityBlockingQueue: 支持优先级排序|***阻塞队列DelayQueue:优先级队列实现|***阻塞队列SynchronousQueue:不存储元素| 阻塞队列LinkedTransferQueue:链表结构组成|***阻塞队列LinkedBlockingDeque:链表结构组成|双向阻塞队列
java7提供的一个用于并行执行任务的框架,把一个大任务分割成若干个小任务,最终汇总每一个小任务结果的后获得大任务结果的框架。详细阅读:Java7任务并行执行神器:Fork&Join框架
是指某个线程从其余队列里窃取任务来执行。当大任务被分割成小任务时,有的线程可能提早完成任务,此时闲着不如去帮其余没完成工做线程。此时能够去其余队列窃取任务,为了减小竞争,一般使用双端队列,被窃取的线程从头部拿,窃取的线程从尾部拿任务执行。
优势:充分利用线程进行并行计算,减小了线程间的竞争。缺点:有些状况下仍是存在竞争,好比双端队列中只有一个任务。这样就消耗了更多资源。
AtomicBoolean:原子更新布尔类型AtomicInteger:原子更新整形AtomicLong:原子更新长整形
AtomicIntegerArray: 原子更新整形数据里的元素AtomicLongArray: 原子更新长整形数组里的元素AtomicReferenceArray: 原子更新饮用类型数组里的元素AtomicIntegerArray: 主要提供原子方式更新数组里的整形
若是原子须要更新多个变量,就须要用引用类型了。AtomicReference : 原子更新引用类型AtomicReferenceFieldUpdater: 原子更新引用类型里的字段。AtomicMarkableReference: 原子更新带有标记位的引用类型。标记位用boolean类型表示,构造方法时AtomicMarkableReference(V initialRef,boolean initialMark)
AtomiceIntegerFieldUpdater: 原子更新整形字段的更新器AtomiceLongFieldUpdater: 原子更新长整形字段的更新器AtomiceStampedFieldUpdater: 原子更新带有版本号的引用类型,将整数值
提供并发控制手段:CountDownLatch、CyclicBarrier、Semaphore线程间数据交换: Exchanger
容许一个或多个线程等待其余线程完成操做。CountDownLatch的构造函数接受一个int类型的参数做为计数器,你想等待n个点完成,就传入n。两个重要的方法:countDown() : 调用时,n会减1。await() : 调用会阻塞当前线程,直到n变成0。await(long time,TimeUnit unit) : 等待特定时间后,就不会继续阻塞当前线程。tips:计数器必须大于等于0,当为0时,await就不会阻塞当前线程。不提供从新初始化或修改内部计数器的值的功能。
可循环使用的屏障。让一组线程到达一个屏障(也能够叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,全部被屏障拦截的线程才会继续运行。CyclicBarrier默认构造放时CyclicBarrier(int parities) ,其参数表示屏障拦截的线程数量,每一个线程调用await方法告诉CyclicBarrier我已经到达屏障,而后当前线程被阻塞。
CountDownLatch:计数器:计数器只能使用一次。等待:一个线程或多个等待另外n个线程完成以后才能执行。CyclicBarrier:计数器:计数器能够重置(经过reset()方法)。等待:n个线程相互等待,任何一个线程完成以前,全部的线程都必须等待。
用来控制同时访问资源的线程数量,经过协调各个线程,来保证合理的公共资源的访问。应用场景:流量控制,特别是公共资源有限的应用场景,好比数据连接,限流等。
Exchanger是一个用于线程间协做的工具类,它提供一个同步点,在这个同步点上,两个线程能够交换彼此的数据。好比第一个线程执行exchange()方法,它会一直等待第二个线程也执行exchange,当两个线程都到同步点,就能够交换数据了。通常来讲为了不一直等待的状况,可使用exchange(V x,long timeout,TimeUnit unit),设置最大等待时间。Exchanger能够用于遗传算法。
几乎全部须要异步或者并发执行任务的程序均可以使用线程池。合理使用会给咱们带来如下好处。
一、判断核心线程池里的线程是否都有在执行任务,否->建立一个新工做线程来执行任务。是->走下个流程。二、判断工做队列是否已满,否->新任务存储在这个工做队列里,是->走下个流程。三、判断线程池里的线程是否都在工做状态,否->建立一个新的工做线程来执行任务,是->走下个流程。四、按照设置的策略来处理没法执行的任务。详细阅读:java高级应用:线程池全面解析。
public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
1.corePoolSize:核心线程池大小,当提交一个任务时,线程池会建立一个线程来执行任务,即便其余空闲的核心线程可以执行新任务也会建立,等待须要执行的任务数大于线程核心大小就不会继续建立。
2.maximumPoolSize:线程池最大数,容许建立的最大线程数,若是队列满了,而且已经建立的线程数小于最大线程数,则会建立新的线程执行任务。若是是***队列,这个参数基本没用。
3.keepAliveTime: 线程保持活动时间,线程池工做线程空闲后,保持存活的时间,因此若是任务不少,而且每一个任务执行时间较短,能够调大时间,提升线程利用率。
4.unit: 线程保持活动时间单位,天(DAYS)、小时(HOURS)、分钟(MINUTES、毫秒MILLISECONDS)、微秒(MICROSECONDS)、纳秒(NANOSECONDS)
5.workQueue: 任务队列,保存等待执行的任务的阻塞队列。
通常来讲能够选择以下阻塞队列:
ArrayBlockingQueue:基于数组的有界阻塞队列。
LinkedBlockingQueue:基于链表的阻塞队列。
SynchronizedQueue:一个不存储元素的阻塞队列。
PriorityBlockingQueue:一个具备优先级的阻塞队列。
6.threadFactory:设置建立线程的工厂,能够经过线程工厂给每一个建立出来的线程设置更有意义的名字。
7.handler: 饱和策略也叫拒绝策略。当队列和线程池都满了,即达到饱和状态。因此须要采起策略来处理新的任务。默认策略是AbortPolicy。
AbortPolicy:直接抛出异常。
CallerRunsPolicy: 调用者所在的线程来运行任务。
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,直接丢掉。
固然能够根据本身的应用场景,实现RejectedExecutionHandler接口自定义策略。
93.向线程池提交任务
可使用execute()和submit() 两种方式提交任务。
execute():无返回值,因此没法判断任务是否被执行成功。
submit():用于提交须要有返回值的任务。线程池返回一个future类型的对象,经过这个future对象能够判断任务是否执行成功,而且能够经过future的get()来获取返回值,get()方法会阻塞当前线程知道任务完成。get(long timeout,TimeUnit unit)能够设置超市时间。
94.关闭线程池
能够经过shutdown()或shutdownNow()来关闭线程池。它们的原理是遍历线程池中的工做线程,而后逐个调用线程的interrupt来中断线程,因此没法响应终端的任务能够能永远没法中止。
shutdownNow首先将线程池状态设置成STOP,而后尝试中止全部的正在执行或者暂停的线程,并返回等待执行任务的列表。
shutdown只是将线程池的状态设置成shutdown状态,而后中断全部没有正在执行任务的线程。
只要调用二者之一,isShutdown就会返回true,当全部任务都已关闭,isTerminaed就会返回true。
通常来讲调用shutdown方法来关闭线程池,若是任务不必定要执行完,能够直接调用shutdownNow方法。
95.线程池如何合理设置
配置线程池能够从如下几个方面考虑。
cpu密集型能够配置可能小的线程,好比 n + 1个线程。
io密集型能够配置较多的线程,如 2n个线程。
混合型能够拆成io密集型任务和cpu密集型任务,
若是两个任务执行时间相差大,否->分解后执行吞吐量将高于串行执行吞吐量。
否->不必分解。
能够经过Runtime.getRuntime().availableProcessors()来获取cpu个数。
建议使用有界队列,增长系统的预警能力和稳定性。
96.Executor
从JDK5开始,把工做单元和执行机制分开。工做单元包括Runnable和Callable,而执行机制由Executor框架提供。
97.Executor框架的主要成员
ThreadPoolExecutor :能够经过工厂类Executors来建立。
能够建立3种类型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool、CachedThreadPool。
ScheduledThreadPoolExecutor :能够经过工厂类Executors来建立。
能够建立2中类型的ScheduledThreadPoolExecutor:ScheduledThreadPoolExecutor、SingleThreadScheduledExecutor
Future接口:Future和实现Future接口的FutureTask类来表示异步计算的结果。
Runnable和Callable:它们的接口实现类均可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。Runnable不能返回结果,Callable能够返回结果。
98.FixedThreadPool
可重用固定线程数的线程池。查看源码:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());}
corePoolSize 和maxPoolSize都被设置成咱们设置的nThreads。
当线程池中的线程数大于corePoolSize ,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止,若是设为0,表示多余的空闲线程会当即终止。
工做流程:
1.当前线程少于corePoolSize,建立新线程执行任务。
2.当前运行线程等于corePoolSize,将任务加入LinkedBlockingQueue。
3.线程执行完1中的任务,会循环反复从LinkedBlockingQueue获取任务来执行。
LinkedBlockingQueue做为线程池工做队列(默认容量Integer.MAX_VALUE)。所以可能会形成以下赢下。
1.当线程数等于corePoolSize时,新任务将在队列中等待,由于线程池中的线程不会超过corePoolSize。
2.maxnumPoolSize等于说是一个无效参数。
3.keepAliveTime等于说也是一个无效参数。
4.运行中的FixedThreadPool(未执行shundown或shundownNow))则不会调用拒绝策略。
5.因为任务能够不停的加到队列,当任务愈来愈多时很容易形成OOM。
99.CachedThreadPool
根据须要建立新线程的线程池。查看源码:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
corePoolSize设置为0,maxmumPoolSize为Integer.MAX_VALUE。keepAliveTime为60秒。
工做流程:
1.首先执行SynchronousQueue.offer (Runnable task)。若是当前maximumPool 中有空闲线程正在执行S ynchronousQueue.poll(keepAliveTIme,TimeUnit.NANOSECONDS),那么主线程执行offer操做与空闲线程执行的poll操做配对成功,主线程把任务交给空闲线程执行,execute方 法执行完成;不然执行下面的步骤2。
2.当初始maximumPool为空或者maximumPool中当前没有空闲线程时,将没有线程执行 SynchronousQueue.poll (keepAliveTime,TimeUnit.NANOSECONDS)。这种状况下,步骤 1将失 败。此时CachedThreadPool会建立一个新线程执行任务,execute()方法执行完成。
3.在步骤2中新建立的线程将任务执行完后,会执行SynchronousQueue.poll (keepAliveTime,TimeUnit.NANOSECONDS)。这个poll操做会让空闲线程最多在SynchronousQueue中等待60秒钟。若是60秒钟内主线程提交了一个新任务(主线程执行步骤1),那么这个空闲线程将执行主线程提交的新任务;不然,这个空闲线程将终止。因为空闲60秒的空闲线程会被终止,所以长时间保持空闲的CachedThreadPool不会使用任何资源。
通常来讲它适合处理时间短、大量的任务。
原做者:Java技术栈
原文连接: 99 道 Java 多线程面试题,看完我跪了!
原出处:公众号