目录
CountDownLatch/CyclicBarrier/Semaphore
volatile
问题:谈谈对volatile的理解?
volatile是Java虚拟机轻量级的同步机制。基本遵守了JMM规范,保证可见性,不保证原子性,禁止指令重排。
三大特性:
- 保证可见性
JMM(Java内存模型)
工作内存是高速缓存。比直接从内存读取数据快很多
在Java内存模型中,由于Java运行程序的实体是线程,每创建一个线程的时候,JVM都会为其创建一个工作内存,工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问,单线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存中拷贝进自己的内存空间,然后对变量进行操作,操作完成后再将变量刷回主内存。不能直接操作内存中的变量,各个线程中的工作内存中存储着主内存中的变量拷贝副本,因此不同线程间无法访问对方的工作内存,线程间的通信必须通过主内存来完成。
什么是可见性?可见性指当一个线程修改了共享变量的值,其他线程能够立刻得知这个修改。Java内存模型是通过变量修改后将新值同步回内存,在变量读取前从主内存刷新变量值来实现可见性的。三种实现可见性的方式:volatile、synchornized,final。
- 不保证原子性
什么是原子性?原子性是指不可分割、完整性,也即当某个线程在做某个具体任务的时候,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败。
- 禁止指令重排
什么是有序性?有序性是指,在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排。在Java内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。指令重排会影响数据的依赖关系。 实现有序性除了volatile和synchornized之外,还规定了先行发生原则来保证有序性。(在Java内存模型中,允许编译器和处理器对指令进行重排,指令重排对单线程没有影响,但是在多线程中会影响并发执行的顺序性。
volatile 关键字通过添加内存屏障的方式来禁止指令重排,通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障还可以保持变量的可见性(强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本)。
你在哪些地方用到过volatile?
单例模式;读写锁手写一个缓存;JUC包中底层大规模使用。
使用双重校验单例模式基本能解决线程安全问题,但是由于指令重拍存在,也可能存在线程不安全。将唯一的单例使用volatile修饰,保证多线程间的语义一致性。
CAS
知识链:CAS->Unsafe->CAS底层思想->ABA->原子引用更新->如何规避ABA问题。
问题:讲一讲AtomicInteger,为什么要用CAS而不是synchornized?
CAS是什么?CompareAndSet比较并交换。比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较知道主内存中的值和工作内存中的值一致为止。CAS有三个操作数,内存知V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
它的功能是判断内存中某个位置的值是否为预期值,如果是则更改为新的这个值,这个过程是原子的。 CAS是一条CPU的原子指令,不会造成所谓的数据不一致。
CAS底层原理:unsafe类和CAS思想(自旋)
AtomicInteger原子类就是使用CAS实现的线程安全的类。可在多线程下保证原子性。
AtomicInteger自增方法:使用CAS实现。
public final int getAndIncrement() {
//this代表当前对象,valueOffset内存偏移量(内存地址)
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//获取当前对象在指定地址上的值
var5 = this.getIntVolatile(var1, var2);
//当修改失败的时候一直循环
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//获取对象var1在var2地址上的值
public native int getIntVolatile(Object var1, long var2);
//如果对象var1在地址var2上的值为var4则将其对象var1在var2上的地址值设置为var5,返回true。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
Unsafe类是什么?
是CAS类的核心,由于Java方法中无法直接访问底层系统,
需要通过本地方法(native)来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存中的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源进行相应任务。
变量valueOffSet,表示该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
变量value使用volatile修饰,保证了多线程之间的内存可见性。
CAS缺点?
- 循环时间长,CPU开销大(比较不成功会一直循环,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大开销)
- 只能保证一个共享变量的原子操作。(多个共享变量的原子性操作只能通过加锁来实现)。
- ABA问题。
ABA问题?
问题:原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?
ABA问题: 如果一个变量初次读取的是A值,它的值被改成了B,后来又被改回为A,CAS操作则会认为它从来没有被改变的。
原子引用?
把某个来包装为原子类型。
class User{
String user;
int age;
public User(String user, int age) {
this.user = user;
this.age = age;
}
}
public class test {
public static void main(String[] args) {
User z3 = new User("z3",22);
User li4 = new User("LI4",25);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3,li4)+"\t"+atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3,li4)+"\t"+atomicReference.get().toString());
}
}
true User@4554617c
false User@4554617c
ABA问题的解决?
AtomicStampedReference(带时间戳的原子引用),可以通过控制变量值的版本来保证CAS的正确性。
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
private volatile Pair<V> pair;
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
集合类不安全
问题:我们知道ArrayList是线程不安全的,请编码写一个线程不安全的案例并给出解决方案?
ArrayList底层是由动态数组来实现的,默认的初始容量为10,扩容时扩大为原来的1.5倍
- 并发修改异常
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//java.util.ConcurrentModificationException
for (int i = 1; i <= 30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
故障现象:多线程对list进行修改的时候,会抛出 java.util.ConcurrentModificationException异常(并发修改异常)
Exception in thread "4" Exception in thread "5" Exception in thread "13" Exception in thread "26" java.util.ConcurrentModificationException
导致原因:并发争抢修改导致。当一个线程在使用list并且还没有完成相应操作的时候,其它线程拿到了list进行使用。
解决方法:
- Vector对数据操作加了锁,能解决线程安全问题,但并发行不好。(不使用);
- Collections.synchronizedList(new ArrayList<>());使用集合工具类,将ArrayList构建成一个同步的List
- CopyOnWriteArrayList。使用Java并发包下的CopyOnWriteArrayList。
优化建议:
- 写时复制
CopyOnWriteArrayList
写时复制:读写分离
维护一个数组,用transient和volatile修饰。
private transient volatile Object[] array;
add方法:
写操作在一个复制的数组上进行,读操作还是在原始的数组中进行。读写分离,互不影响。
写操作需要加锁(juc包中的ReentrantLock),放置并发写入时导致写入数据丢失。
写操作结束之后,将原始数组指向新的复制数组。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
缺点:内存占用;数据不一致(读操作不能实时读取到最新的数据,写的数据可能还没同步)
- Set
HashSet线程不安全,也会导致 java.util.ConcurrentModificationException异常。
解决方法:
- 使用集合工具类包装成一个线程安全的Set
Collections.synchronizedSet(new HashSet<>());
- 使用CopyOnWriteArraySet
Set<String> list = new CopyOnWriteArraySet<>();
底层转换成CopyOnWriteArrayList实现相应接口
private final CopyOnWriteArrayList<E> al;
/**
* Creates an empty set.
*/
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
HashSet的底层数据结构就是hashMap
private transient HashMap<E,Object> map;
add方法:hashset的add的元素是底层hashmap的key,值为PRESENT,一个Object类型的常量
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();
- Map
HashMap多线程修改时也会抛java.util.ConcurrentModificationException异常(并发修改异常)
解决方法:
- 使用集合工具类COllections包装成线程安全的map
Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
公平锁/非公平锁/可重入锁/递归锁/自旋锁
问题:公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁。
- 公平锁和非公平锁
例如ReentrantLock
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
默认为非公平锁,构造函数传入true为公平锁。
公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。在高并发情况下,有可能会造成优先级反转或饥饿现象(优先级较低的线程一直不能获取锁)。
ReentrantLock默认是非公平锁,可以通过构造函数传入true设置为公平锁,synchornized是非公平锁。
两者的区别:
公平锁:在并发环境中,每个线程获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就是加入等待队列种,以后会按照FIFO的规则从队列种取到自己。
非公平锁:非公平锁比较粗鲁,上来就直接占有锁,如果尝试失败,就再才有类似公平锁那种方式。
非公平锁的优点在于吞吐量比公平锁大。
吞吐量:单位时间内成功传送数据的数量。
- 可重入锁(递归锁)
指的是同一线程在外层函数获得锁之后,内层递归函数仍然能获得该锁的同步代码。在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。
Reentrantlock和synchornized都是可重入锁。
可重入锁的最大优点是避免死锁。
- 自旋锁
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少上下文切换的消耗,缺点是循环会消耗cpu。CAS操作就是一直循环获取锁。
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//获取当前对象在指定地址上的值
var5 = this.getIntVolatile(var1, var2);
//当修改失败的时候一直循环
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
好处:循环比较直到获取成功为止,没有类似wait的阻塞。
- 独占锁(写锁)/共享锁(读锁)/互斥锁
独占锁:该锁一次只能被一个线程持有。对ReentrantLock和sychornized而言都是独占锁
共享锁:该锁可被多个线程持有。
ReentrantReadWriteLock:其读锁是共享,其写锁是独占。
CountDownLatch/CyclicBarrier/Semaphore
问题:CountDownLatch/CyclicBarrier/Semaphore使用过吗?
- CountDownLatch:
让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒。
CountDownLatch主要有两个方法:当一个线程调用await方法时,调用线程会被阻塞,其它线程调用countdown方法会将计数器减1(调用countdown方法线程不会阻塞,当计数器的值变为0的时候,因调用await方法被阻塞的线程会被唤醒,继续执行。(秦灭六国,一统天下)
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t上完自习离开教室");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t班长最后关门走人");
}
- CyclicBarrier
CyclicBarrier的字面意思时循环使用的屏障。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障,屏障才会开门,所有被拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法(集齐7颗龙珠召唤神龙)
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{System.out.println(("召唤神龙"));});
for (int i = 1; i <= 7; i++) {
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 收集到第"+temp+"课龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
- Semaphore
信号量:主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(3);//模拟3个停车位
for (int i = 0; i <= 6; i++) {//模拟6部车
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"\t抢到车位");
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t停了3秒钟后离开");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
阻塞队列
问题:阻塞队列知道吗?
队列:FIFO
阻塞队列:当阻塞队列是空的时候,从队列种获取元素的操作将会被阻塞。当阻塞队列是满的时候,往队列里添加元素的操作将会被阻塞。
为什么需要BlockingQueue:好处是我们不需要关心什么时候阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue给包办了。
JUC包种的BlockingQueue接口是Collection的子接口。
其实现类主要有以下7个:
- ArrayBlockingQueue
- LinkedBlockingQueue
- SynchornousBlockingQueue
阻塞队列的核心方法:
阻塞队列应用场景:生产者消费者模式,线程池,消息中间件。
synchornized和lock的区别
问题:sychoenized和lock有什么区别?用新的lock有什么好处?你举例说说。
- 原始构成
sychornized是关键字,属于JVM层面。monitorenter,monitorexit(底层是通过monitor对象来完成的,其实wait和notify方法也依赖于monitor对象只有在同步方法块种才能用wait和notify方法)。
Lock是具体类(java.util.concurrent.locks.lock)是api层面的
- 使用方法
sychornized不需要用户去手动释放锁,当sychornized代码执行完后系统会自动让线程释放对锁的占用。
ReentrantLock则需要用户去手动释放锁,若没有主动释放,就有可能导致死锁现象。
- 等待是否可中断
sychornized不可中断,除非抛出异常或者正常运行完成。
ReentrantLock可中断,1.设置超时方法trylock(long timeout,TimeUnit unit) 2.lockInterruptibly()放代码块种,调用interrupt()方法可中断。
- 加锁是否公平
sychornized是非公平锁
ReentrantLock两者都可以,默认为非公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁。
- 锁绑定多个条件
sychornized没有
ReentrantLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像sychornized要么随机唤醒一个线程要么唤醒全部线程。
Condition使用实例
/**
* 题目:多线程下按顺序调用,实现A->B->C三个线程启动,要求如下:
* AA打印5次,BB打印10次,CC打印15次
* 上面的重复10轮
*/
class ShareResource{
private int number = 1;//A:1 B:2 C:3
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void print5(){
lock.lock();
try{
while (number!=1){
condition1.await();
}
for (int i = 1; i <=5 ; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number = 2;
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void prin10(){
lock.lock();
try{
while (number!=2){
condition2.await();
}
for (int i = 1; i <=10 ; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number = 3;
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void prin15(){
lock.lock();
try{
while (number!=3){
condition3.await();
}
for (int i = 1; i <=15 ; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number = 1;
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
/**
* 线程操纵资源类
* 判断,等待,干活
* 防止虚假唤醒
*/
public class test2 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 1; i <= 10 ; i++) {
shareResource.print5();
}
},"A").start();
new Thread(()->{
for (int i = 1; i <= 10 ; i++) {
shareResource.prin10();
}
},"B").start();
new Thread(()->{
for (int i = 1; i <= 10 ; i++) {
shareResource.prin15();
}
},"C").start();
}
}
原始版生产者消费者实现
/**
* 初始值为0的变量,两个线程对其交替进行操作,一个加1,一个减1,来5轮
*/
class ShareData{
private int number = 0; //等于0时可以增加,等于1时可以减少
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
lock.lock();
try {
//1 判断
while (number!=0){
//等待,不能生产
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decrement(){
lock.lock();
try {
//1 判断
while (number!=1){
//等待,不能生产
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class 原始版生产者消费者模式 {
public static void main(String[] args) {
ShareData shareData = new ShareData();
for (int i = 1; i <= 5; i++) {
new Thread(()->{
try {
shareData.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
for (int i = 1; i <= 5 ; i++) {
new Thread(()->{
shareData.decrement();
},String.valueOf(i)).start();
}
}
}
阻塞队列版生产者消费者实现
class ShareData1{
//true表示开始生产+消费
private volatile boolean flag = true;
//生产消费过程是原子操作
private AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(5);
public void myProd() throws InterruptedException {
String data = null;
boolean returnVal;
while (flag){
data = atomicInteger.incrementAndGet()+"";
//将数据放入阻塞队列中
returnVal = blockingQueue.offer(data,2L, TimeUnit.SECONDS);
if(returnVal){
System.out.println(Thread.currentThread().getName()+"\t插入队列"+data+"成功");
}else{
System.out.println(Thread.currentThread().getName()+"\t插入队列"+data+"失败");
}
TimeUnit.SECONDS.sleep(1L);
}
//flag为false停止生产
System.out.println(Thread.currentThread().getName()+"\t大老板叫停");
}
public void myConsumer() throws InterruptedException {
String result = null;
while (flag){
result = blockingQueue.poll(2L,TimeUnit.SECONDS);
//如果没有取到
if(null==result||result.equalsIgnoreCase("")){
flag = false;
System.out.println(Thread.currentThread().getName()+"\t超过2秒钟没有取到蛋糕,消费退出");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName()+"\t消费队列"+result+"成功");
}
System.out.println(Thread.currentThread().getName()+"\t大老板叫停");
}
public void stop(){
this.flag = false;
}
}
public class 生产者消费者模式阻塞队列版 {
public static void main(String[] args) throws InterruptedException {
ShareData1 shareData1 = new ShareData1();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t生产线程启动");
try {
shareData1.myProd();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Prod").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t消费线程启动");
try {
shareData1.myConsumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Consumer").start();
TimeUnit.SECONDS.sleep(5L);
shareData1.stop();
}
}
实现线程的三种方式
- 第一种:继承Thread实现run方法
- 第二种:实现Runable接口,并将实现类的实例传给Thread的构造方法。还是要通过thread的start方法来调用。
- 第三种:实现Callable接口,实现Call方法,并且有返回值,call方法会抛异常
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 1024;
}
}
public class callable接口 {
MyThread myThread = new MyThread();
FutureTask<Integer> futureTask = new FutureTask<>(myThread);
Thread t1 = new Thread(futureTask);
}
线程池
问题:线程池用过吗?ThreadPoolExecutor谈谈你的理解?
问题:线程池用过吗?生产上你如何设置合理参数?
线程池的工作主要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
主要特点为:线程复用;控制最大并发数;管理线程。
第一:降低资源消耗。通过重复利用已经创建的线程降低线程创建和销毁造成的消耗
第二:提高响应的速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
第三:提高线程的客观理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行同一分配,调优和监控。
线程池的架构:
Executors是Excutor的辅助工具
- 线程池的种类-5种
(了解)
Executors.newScheduledThreadPool()
Executors.newWorkStealingPool(int)-java8新出
重点
Executors.newFixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
主要特点:
- 创建一个定长线程池,可控制最大并发数量,超出的线程会在队列中等待
- newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue
执行长期的任务,性能好很多
Executors.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
主要特点:
- 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行
- newSingleThreadExecutor创建的线程池corePoolSize和maximumPoolSize值都是1,它使用的LinkedBlockingQueue
一个任务一个任务执行的场景
Executors.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
主要特点:
- 创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
- newCachedThreadPoolr创建的线程池corePoolSize为0,maximumPoolSize设置为Integer.MAX_VALUE,使用SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
执行很多短期异步的小程序或者负载较轻的服务
public class test {
public static void main(String[] args) throws InterruptedException {
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// ExecutorService threadPool = Executors.newCachedThreadPool();
ExecutorService threadPool = new ThreadPoolExecutor(2,5,1L,
TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try{
//模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
for (int i = 1; i <= 8; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
}
线程池的7个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- corePoolSize:线程池中的常驻核心线程数
- maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
- keepAliveTime:多余的空闲线程的存活时间。当线程数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止
- unit:keepAliveTime的单位
- workQueue:任务队列,被提交但尚未被执行的任务
- threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认即可
- handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数maximumPoolSize时如何来拒绝请求执行的runable的策略。
线程池的底层工作原理
线程池的拒绝策略
等待队列也已经满了,不能接收新的任务了,同时线程池中的max线程也达到了,无法继续为新任务服务,需要拒绝策略机制合理的处理这个问题。
jdk内置的4种拒绝策略
- AbortPolicy(默认):直接抛出RejectExecutionException异常组织系统正常运行
- CallerRunsPolicy:“调用者运行“一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务退回到调用者,从而减低新任务的流量。
- DiscardOldestPolicy:抛弃等待队列种等待最久的任务,然后把当前任务加入队列种尝试再次提交当前任务
- DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
以上内置拒绝策略均实现了RejectExecutionHandler接口
线程池选择
问题:你在工作中单一的/固定数的/可变的三种创建线程池的方法,你用哪个最多?
一个都不用,生产上只能使用自定义的。为什么不用?
请求堆积(任务队列使用的是无界的阻塞队列(LinkedBlockingQueue(有界为Integer.MAX_VALUE相当于有界))和创建大量的线程的问题
问题:你在工作中是如何使用线程池的,是否自定义过线程池使用?
自定义线程池
问题:合理配置线程池你是如何考虑的?
先看服务器是几核的
- CPU密集型
- IO密集型
死锁编码及定位分析
- 什么是死锁
- 产生死锁的主要原因
- 解决
jps命令定位进程号
jstack找到死锁查看