Volatile是什么
相关包Package java.ytil.concurrent(并发包)
Package java.ytil.concurrent.atomic(原子性)
AtomicInteger,原子引用
Volatile是jvm提供的轻量级的同步机制
三大特性:1保证可见性、2、不保证原子性、3禁止指令重拍
JMM(java内存模型)
由于JVM运行程序的实体是线程、而每个线程创建时JVM都会为其创建一个工作内存(有些地方称之为栈空间),工作内存是每个线程的私有数据区域、而java内存模型中规定所有变量都存在主内存,主内存是共享内存区域、所有线程都可以访问、但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要讲变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作、操作完成后再讲变量协会主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中变量副本拷贝、因此不同的线程间无法访问对方的工作内存、线程就的通信(传值)必须通过主内存来完成。
可见性:当进行多线程开发的时候。多个线程会把主内存中的值复制一份。当其中一个线程的值修改了。需要拷贝回给主内存。而主内存需要通知给其他的线程内存。这个过程就被称之为可见性。
JMM:1)可见性、2)原子性3)有序性
有代码来验证
class ThreadCode {
// volatile int num=0;
int num=0;
public void addnum(){
this.num=60;
}
}
/**
* 1、验证volatile可见性
*/
@Test
void contextLoads() {
ThreadCode threadCode=new ThreadCode();
//1号线程
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"come in");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadCode.addnum();
System.out.println(Thread.currentThread().getName()+"update number value "+threadCode.num);
},"AAA").start();
//2号线程
while (threadCode.num==0)
{
//main线程就一直在这里等待循环、直到num值不再等于零
}
System.out.println(Thread.currentThread().getName()+"mission is over");
}
没有volatile关键字就该为60后永久的循环下去了
有volatile 就可以获取到main is over
原子性
原子性是什么意思:不可分割,完整性、也就是某个线程正在做某个具体业务的时候中间不能被加塞或者被分割。需要整体完成。要么同时成功、要么同时失败。
@Test
void atomicNoByVolatile()
{
ThreadCode threadCode=new ThreadCode();
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 2000; j++) {
threadCode.addplus();
}
},String.valueOf(i)).start();
}
//等待20个线程全部计算完成后,再取得最终的结果值
while (Thread.activeCount()>2){
Thread.yield();
}
try {
System.out.println(Thread.currentThread().getName()+"\t finally num \t"+threadCode.num);
} catch (Exception e) {
e.printStackTrace();
}
}
加了volatile,数字会随机变化。说明线程间产生了影响
方法上有synchronized
public synchronized void addplus()
{
num++;
}
除了synchronized 如何解决原子性。可以使用atomic类的
AtomicInteger atomicInteger=new AtomicInteger();
public void addAtomic(){
atomicInteger.getAndIncrement();
}
禁止指令重排
计算机在执行程序时:为了提高性能、编译器和处理器的常常会对指令进行重排,
源代码-》编译器优化的重排-》指令并行的重排-》内存系统的重排-》最终执行的指令
单线程环境里边确保程序最终执行结果和代码顺序执行的结果一致
处理器在进行重排序时要考虑指令间的数据依赖性
多环境下线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证异质性是无法确定的,结果无法预测。
CAS
CAS( CompareAndSet)比较并交换一条CPU并发原语
1、unsafe
是CAS的核心类。由于JAVA方法无法直接访问底层系统、需要通过本地(native)方法来访问、Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe其内部方法可以像C的指针一样直接操作内存、因为java中的CAS操作的执行依赖于Unsafe的类方法。
注意Unsafe类中的所有方法都是native修饰的。也就是说unsafe中的方法都直接调用操作系统底层资源执行相应的任务。
缺点:
1、循环时间长,开销大
2、只能保证一个共享变量原子操作
3、引出来ABA问题
解决ABA问题
理解原子引用、新增一种机制,那就是修改版本号(类似时间戳)
JUC,AtomicStampedReference
AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100, 1);
@Test
void ABATest() {
System.out.println("==============以下是ABA的产生================");
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
System.out.println("ABA第一次的值\t" + atomicReference.get());
atomicReference.compareAndSet(101, 100);
System.out.println("ABA第二次的值\t" + atomicReference.get());
}, "T1").start();
new Thread(() -> {
try {
//线程暂停1秒钟,保证T1完成一次ABA
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
}, "T2").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("==============以下是ABA的解决================");
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + stamp);
//获取版本号暂停1秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第二次版本号" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第三次版本号" + atomicStampedReference.getStamp());
}, "T3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + atomicStampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean res = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改成功否" + res+"\t当前版本号:"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t当前值" + atomicStampedReference.getReference());
}, "T4").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
集合类不安全的问题
1、故障现象:
ArrayList<>的线程不安全,当并发到了一定级别后
报错:java.util.ConcurrentModificationException
2、导致原因
并发争抢修改导致、一个人正在写、另外一个人抢过来写。导致数据情况不一致。
3、解决方法
1、new Vector<>();
2、Collections.synchronizedList(new ArrayList<>());
3、new CopyOnWriteArrayList<>();
CopyOnWrite写时复制:往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器进行copy,复制出一个新的容器,然后新的容器里添加元素,添加完成后,再将原容器的引用指向新的容器,这样做的好处是可以对copyonwrite进行并发的读,而不需要加锁,因为当前容器不会添加任何元素、所以copyOnwirte容器耶是一种读写分离的思想、读和写不同的容器
public boolean add(E e) {
synchronized(this.lock) {
Object[] es = this.getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
this.setArray(es);
return true;
}
}
HashSet底层是HashMap
set在add的时候会有一个常量PRESENT,默认是一个Object
private static final Object PRESENT = new Object();
public boolean add(E e) {
return this.map.put(e, PRESENT) == null;
}
公平锁和非公平锁
公平锁:是指多个线程按照申请锁的顺序来获取锁、类似排队打饭,先来后到
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁在高并发的情况下,有可能会造成优先级翻转或者饥饿现象。
公平锁/非公平锁
并发包ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或者非公平锁 默认是非公平锁
Java ReentrantLock而言,
通过构造哈数指定该锁是否是公平锁 默认是非公平锁 非公平锁的优点在于吞吐量必公平锁大.
对于synchronized而言 也是一种非公平锁.
可重入锁
可以避免死锁,ReentrantLock/synchronized就是一个典型的可重入锁
自旋锁
/**
* 自旋锁好处:循环比较获取直到成功为止,没有类似wait的阻塞
*
* 通过CAS操作完成自旋锁、A线程先进来调用MYLOCK方法自己持有锁5秒钟,B随后进来后发现当前有线程持有锁、不是NULL,所以只能通过自选等待、知道A释放锁后B随后抢到
*/
public class SpinLockDemo {
//原子引用线程
AtomicReference<Thread> atomicReference=new AtomicReference<>();
public void myLock() {
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t come in");
while(!atomicReference.compareAndSet(null,thread)){
}
}
public void myUnLock() {
Thread thread=Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t invoked myUnLock()");
}
}
@Test
void SpinTest(){
SpinLockDemo spinLockDemo=new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnLock();
},"T1").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
spinLockDemo.myLock();
spinLockDemo.myUnLock();
},"T2").start();
}
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
读写锁。JUC
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t正在写入" + key);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public Object get(String key) {
Object res = null;
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t正在写入" + key);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
res = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
return res;
}
public void clear(){
map.clear();
}
}
AQS 原理概览
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
状态信息通过protected类型的getState,setState,compareAndSetState进行操作
//返回同步状态的当前值
protected final int getState() {
return state;
}
// 设置同步状态的值
protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
AQS定义两种资源共享方式
Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
CountDownLatch
CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,调用线程会被阻塞.其他线程调用countDown方法计数器减1(调用countDown方法时线程不会阻塞),当计数器的值变为0,因调用await方法被阻塞的线程会被唤醒,继续执行
public void banzhang() throws InterruptedException {
CountDownLatch countDownLatch=new CountDownLatch(6);
for (int i = 0; i <6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t上完自习、离开教室");
countDownLatch.countDown();
},"T1").start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t班长最后走人");
}
CyclicBarrier
CyclicBarrier的字面意思是可循环(Cyclic) 使用的屏障(barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点)时被阻塞,知道最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法.
public class CyclicBarrierDemo {
public static void main(String[] args) {
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 class SemaphoreDemo {
public static void main(String[] args) {
//模拟3个停车位
Semaphore semaphore = new Semaphore(3);
//模拟6部汽车
for (int i = 1; i <= 6; i++) {
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秒离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放资源
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
阻塞队列
阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如图所示:
当阻塞队列是空时,从队列中获取元素的操作将会被阻塞.
当阻塞队列是满时,往队列中添加元素的操作将会被阻塞.
同样
试图往已满的阻塞队列中添加新圆度的线程同样也会被阻塞,知道其他线程从队列中移除一个或者多个元素或者全清空队列后使队列重新变得空闲起来并后续新增.
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即线程阻塞),一旦条件满足,被挂起的线程优惠被自动唤醒
为什么需要使用BlockingQueue
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为BlockingQueue都一手给你包办好了
在concurrent包 发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度.
情况 | 状态 |
---|---|
抛出异常 | 当阻塞队列满时,再往队列里面add插入元素会抛IllegalStateException: Queue full 当阻塞队列空时,再往队列Remove元素时候回抛出NoSuchElementException |
特殊值 | 插入方法,成功返回true 失败返回false移除方法,成功返回元素,队列里面没有就返回null |
一直阻塞 | 当阻塞队列满时,生产者继续往队列里面put元素,队列会一直阻塞直到put数据or响应中断退出当阻塞队列空时,消费者试图从队列take元素,队列会一直阻塞消费者线程直到队列可用 |
超时退出 | 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出 |
SynchronousQueue
SynchronousQueue没有容量
与其他BlcokingQueue不同,SynchronousQueue是一个不存储元素的BlcokingQueue
每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然.
/**
* Description
* 阻塞队列SynchronousQueue演示
*
* @author veliger@163.com
* @version 1.0
* @date 2019-04-13 13:49
**/
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "\t put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "\t put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + "\t put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AAA").start();
new Thread(() -> {
try {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BBB").start();
}
}
synchronized和lock有什么区别?
利用ReentrantLock中的Condition来实现精准线程唤醒
package com.example.intetviewsec.util;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SyncAndReentrantLockDemo {
private Lock lock=new ReentrantLock();
private Condition c1=lock.newCondition();
private Condition c2=lock.newCondition();
private Condition c3=lock.newCondition();
public void func(){
ShareResource shareResource=new ShareResource(lock);
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareResource.printkk(1,c1,c2);
}
},"T1").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareResource.printkk(2,c2,c3);
}
},"T2").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareResource.printkk(3,c3,c1);
}
},"T3").start();
}
}
class ShareResource{
private int number=1;
private Lock lock=new ReentrantLock();
protected ShareResource(Lock lock)
{
this.lock=lock;
}
public void printkk(int k, Condition condition1,Condition condition2)
{
lock.lock();
try {
//1\判断
while(number!=k){
condition1.await();
}
//2\干活
for(int i=1;i<=5*k;i++){
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//3\通知
if (k==3)
{
number=1;
}
else
{
number=k+1;
}
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
利用阻塞队列写生产者消费者模式
package com.example.intetviewsec.util;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyResource {
/**
* 默认开启 进行生产消费的交互
*/
private volatile boolean flag = true;
/**
* 默认值是0
*/
private AtomicInteger atomicInteger = new AtomicInteger();
private BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProd() throws Exception {
String data = null;
boolean returnValue;
while (flag) {
data = atomicInteger.incrementAndGet() + "";
returnValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (returnValue) {
System.out.println(Thread.currentThread().getName() + "\t 插入队列数据" + data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t 插入队列数据" + data + "失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "\t 停止 表示 flag" + flag);
}
public void myConsumer() throws Exception {
String result = null;
while (flag) {
result = blockingQueue.poll(2L, TimeUnit.SECONDS);
if(null==result||"".equalsIgnoreCase(result)){
flag=false;
System.out.println(Thread.currentThread().getName()+"\t"+"超过2m没有取到 消费退出");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "消费队列" + result + "成功");
}
}
public void stop() throws Exception{
flag=false;
}
}
public class ProdConsumerBlockQueueDemo {
public void func() throws Exception {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t生产线程启动");
try {
myResource.myProd();
} catch (Exception e) {
e.printStackTrace();
}
},"Prod").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t消费线程启动");
try {
myResource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
},"consumer").start();
try { TimeUnit.SECONDS.sleep(6); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println();
System.out.println();
System.out.println();
System.out.println("时间到,停止活动");
myResource.stop();
}
}
线程池
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务加入队列,然后在线程创建后启动这些任务,如果先生超过了最大数量,超出的数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行.
他的主要特点为:线程复用:控制最大并发数:管理线程.
第一:降低资源消耗.通过重复利用自己创建的线程降低线程创建和销毁造成的消耗.
第二: 提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行.
第三: 提高线程的可管理性.线程是稀缺资源,如果无限的创阿金,不仅会消耗资源,还会较低系统的稳定性,使用线程池可以进行统一分配,调优和监控.
底层原理
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
this.mainLock = new ReentrantLock();
this.workers = new HashSet();
this.termination = this.mainLock.newCondition();
if (corePoolSize >= 0 && maximumPoolSize > 0 && maximumPoolSize >= corePoolSize && keepAliveTime >= 0L) {
if (workQueue != null && threadFactory != null && handler != null) {
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
} else {
throw new NullPointerException();
}
} else {
throw new IllegalArgumentException();
}
}
1.corePoolSize:线程池中的常驻核心线程数
1.在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近视理解为今日当值线程
2.当线程池中的线程数目达到corePoolSize后,就会把到达的任务放入到缓存队列当中.
2.maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值大于等于1
3.keepAliveTime:多余的空闲线程存活时间,当空间时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止
4.unit:keepAliveTime的单位
5.workQueue:任务队列,被提交但尚未被执行的任务
6.threadFactory:表示生成线程池中工作线程的线程工厂,用户创建新线程,一般用默认即可
7.handler:拒绝策略,表示当线程队列满了并且工作线程大于等于线程池的最大显示 数(maxnumPoolSize)时如何来拒绝.
class MyRunnable implements Runnable {
private String command;
public MyRunnable(String s) {
this.command = s;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
processCommand();
System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
}
private void processCommand() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return this.command;
}
}
private static final int CORE_POOL_SIZE = 5;
private static final int MAX_POOL_SIZE = 10;
private static final int QUEUE_CAPACITY = 100;
private static final Long KEEP_ALIVE_TIME = 1L;
@Test
public void TestStr() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 12; i++) {
//创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
Runnable worker = new MyRunnable("" + i);
//执行Runnable
executor.execute(worker);
}
//终止线程池
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
线程池的拒接策略
等待队列也已经排满了,再也塞不下新的任务了
同时,
线程池的max也到达了,无法接续为新任务服务
这时我们需要拒绝策略机制合理的处理这个问题.
AbortPolicy(默认):直接抛出RejectedException异常阻止系统正常运行
CallerRunPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者、从而降低新任务的流量
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交
DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常.如果允许任务丢失,这是最好的拒绝策略
以上内置策略均实现了RejectExecutionHandler接口
你在工作中单一的/固定数的/可变你的三种创建线程池的方法,你用哪个多
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
死锁