JUC
前言
多线程编程是一个优秀程序员必须要掌握的。现在开发讲究”三高“,高可用、高并发、高性能,其中高并发就是对我们并发编程有着很大的考验.并发编程可以更好的提高CPU的利用率.
进程
进程就是一个开启的程序,在Windows系统中一个QQ.exe就是一个进程,在传统的操作系统中,进程即是资源分配的基本单位,也是执行的基本单位.
线程
一个进程中可以包含多个线程(至少包含一个线程,Java线程启动至少包含两个线程,一个main线程,一个GC线程),在引入线程的操作系统中,通常都是把进程作为资源的分配单位,把线程作为运行和调度的基本单位.
线程的不同状态
public enum State {
//新建
NEW,
//运行
RUNNABLE,
//阻塞,可能是因为等待锁
BLOCKED,
//等待,等待CPU调度进行执行
WAITING,
//调用sleep(),join(),wait()方法可能会导致线程处于等待
TIMED_WAITING,
//销毁
TERMINATED;
}
线程的创建方式
-
继承Thread类
public static void main(String[] args) { /** * 线程创建方式一,通过继承Thread类 */ ThreadDemo threadDemo = new ThreadDemo(); new Thread(threadDemo).start(); } class ThreadDemo extends Thread{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
-
实现Runnable接口
/** * 线程创建方式二,通过实现Runnable接口 */ ThreadDemo2 threadDemo2 = new ThreadDemo2(); new Thread(threadDemo2).start(); class ThreadDemo2 implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"通过实现Runnable接口创建了线程"); } } /** * 方法二的另一种写法通过lambda表达式 */ new Thread(() -> { System.out.println(Thread.currentThread().getName()+"通过lambda表达式创建了线程"); }).start();
-
实现Callable接口通过FutureTask包装器来创建Thread线程;
/** * 方法三通过实现Callable接口 注意这个方法是存在返回值的 */ new Thread(new FutureTask<>(() ->{ System.out.println(Thread.currentThread().getName()+"通过Callable接口创建了线程"); return 0; })).start();
-
通过线程池
public static void main(String[] args) { ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 3, 5, 3L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); try { threadPool.execute(() ->{ System.out.println(Thread.currentThread().getName()+"通过线程池创建了线程"); }); } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }
售票器导入synchronized和Lock锁
说完了线程的创建方式接下来通过一个购买火车票的案例来讨论一下什么是并发编程
public class TicketSalesThread { public static void main(String[] args) { TicketSales ticketSales = new TicketSales(); /** * 售票点A */ new Thread(() ->{ for (int i = 0; i < 60; i++) { ticketSales.saleTicket(); } } , "A").start(); /** * 售票点B */ new Thread(() ->{ for (int i = 0; i < 60; i++) { ticketSales.saleTicket(); } } , "B").start(); /** * 售票点C */ new Thread(() ->{ for (int i = 0; i < 60; i++) { ticketSales.saleTicket(); } } , "C").start(); } } /** * 资源类 */ class TicketSales{ /** * 存在50张票 */ private int num = 50; public void saleTicket(){ if (num >0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+(num--)+"张票--->"+"还剩"+num+"张票"); } } } /* A卖出了第50张票--->还剩49张票 A卖出了第49张票--->还剩48张票 A卖出了第48张票--->还剩46张票 B卖出了第47张票--->还剩46张票 B卖出了第46张票--->还剩45张票 B卖出了第45张票--->还剩44张票 B卖出了第44张票--->还剩43张票 B卖出了第42张票--->还剩41张票 A卖出了第43张票--->还剩42张票 A卖出了第40张票--->还剩39张票 A卖出了第39张票--->还剩38张票 B卖出了第41张票--->还剩40张票 B卖出了第37张票--->还剩36张票 B卖出了第36张票--->还剩35张票 B卖出了第35张票--->还剩34张票 A卖出了第38张票--->还剩37张票 A卖出了第32张票--->还剩31张票 A卖出了第31张票--->还剩30张票 A卖出了第30张票--->还剩29张票 A卖出了第29张票--->还剩28张票 A卖出了第28张票--->还剩27张票 A卖出了第27张票--->还剩26张票 B卖出了第33张票--->还剩32张票 C卖出了第34张票--->还剩33张票 B卖出了第25张票--->还剩24张票 A卖出了第26张票--->还剩25张票 A卖出了第22张票--->还剩21张票 B卖出了第23张票--->还剩22张票 C卖出了第24张票--->还剩23张票 B卖出了第20张票--->还剩19张票 A卖出了第21张票--->还剩20张票 B卖出了第18张票--->还剩17张票 C卖出了第19张票--->还剩18张票 B卖出了第16张票--->还剩15张票 B卖出了第14张票--->还剩13张票 B卖出了第13张票--->还剩12张票 B卖出了第12张票--->还剩11张票 A卖出了第17张票--->还剩16张票 B卖出了第11张票--->还剩10张票 B卖出了第9张票--->还剩8张票 B卖出了第8张票--->还剩7张票 B卖出了第7张票--->还剩6张票 B卖出了第6张票--->还剩5张票 B卖出了第5张票--->还剩4张票 B卖出了第4张票--->还剩3张票 B卖出了第3张票--->还剩2张票 B卖出了第2张票--->还剩1张票 C卖出了第15张票--->还剩14张票 B卖出了第1张票--->还剩0张票 A卖出了第10张票--->还剩9张票 */
分析结果很明显是不正确的,当我们卖完第48张票的时候应该是还剩47张,而不是46张,而且剩余的票的数量应该是顺序递减的,而不是跳着卖的,这种问题就是我们在并发编程的时候经常遇见的问题.所以我们这个问题就应该从这个卖票上进行解决,我们可以进行设置当我们一个人进行卖票时其他人应该进行等待。这个时候我们就可以用我们java提供的关键字synchronized进行同步.改进如下:
//核心代码改进如下 public synchronized void saleTicket(){ if (num >0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+(num--)+"张票--->"+"还剩"+num+"张票"); } /* A卖出了第50张票--->还剩49张票 B卖出了第49张票--->还剩48张票 B卖出了第48张票--->还剩47张票 B卖出了第47张票--->还剩46张票 B卖出了第46张票--->还剩45张票 A卖出了第45张票--->还剩44张票 A卖出了第44张票--->还剩43张票 A卖出了第43张票--->还剩42张票 A卖出了第42张票--->还剩41张票 A卖出了第41张票--->还剩40张票 B卖出了第40张票--->还剩39张票 B卖出了第39张票--->还剩38张票 B卖出了第38张票--->还剩37张票 B卖出了第37张票--->还剩36张票 B卖出了第36张票--->还剩35张票 B卖出了第35张票--->还剩34张票 B卖出了第34张票--->还剩33张票 B卖出了第33张票--->还剩32张票 B卖出了第32张票--->还剩31张票 B卖出了第31张票--->还剩30张票 B卖出了第30张票--->还剩29张票 B卖出了第29张票--->还剩28张票 B卖出了第28张票--->还剩27张票 B卖出了第27张票--->还剩26张票 B卖出了第26张票--->还剩25张票 B卖出了第25张票--->还剩24张票 B卖出了第24张票--->还剩23张票 B卖出了第23张票--->还剩22张票 B卖出了第22张票--->还剩21张票 B卖出了第21张票--->还剩20张票 B卖出了第20张票--->还剩19张票 B卖出了第19张票--->还剩18张票 B卖出了第18张票--->还剩17张票 B卖出了第17张票--->还剩16张票 B卖出了第16张票--->还剩15张票 B卖出了第15张票--->还剩14张票 B卖出了第14张票--->还剩13张票 B卖出了第13张票--->还剩12张票 B卖出了第12张票--->还剩11张票 B卖出了第11张票--->还剩10张票 B卖出了第10张票--->还剩9张票 B卖出了第9张票--->还剩8张票 B卖出了第8张票--->还剩7张票 B卖出了第7张票--->还剩6张票 B卖出了第6张票--->还剩5张票 B卖出了第5张票--->还剩4张票 B卖出了第4张票--->还剩3张票 B卖出了第3张票--->还剩2张票 B卖出了第2张票--->还剩1张票 B卖出了第1张票--->还剩0张票 */
我们通过Java内置的关键字synchronized,很好的解决了这个问题。在JDK1.5之后官方推出了Lock锁,在使用上更加灵活.Lock是一个接口,下面存在三个实现类 ReentrantLock() ,Condition(),ReadWriteLock().我们一般使用Lock锁都是通过ReentranLock()进行获取.
synchronized和Lock的区别
从功能上来看synchronized和lock锁都能实现同步,但是二者还是存在较大的差别的。
- synchronized是Java内置的关键字属于JVM层,而Lock是一个接口
- synchronized会自动释放锁,而Lock需要我们在finally里面手动释放锁
- 存在两个线程1和线程2,如果被synchronized修饰,如果线程1发生阻塞的话,线程2会一直等待,而如果用lock锁的话,线程而会尝试获取锁如果获取不到线程而可以不用一直等待就结束了.
- synchronized无法获取锁的状态,而lock锁可以判断是否获取到锁,tryLock()方法
- synchronized 是可重入锁 不可中断的 非公平锁,而Lock是可重入 可中断的 可以公平也可以非公平的(我们在new ReentrantLock(true)默认是false 非公平的)
- lock锁可以通过condition精准唤醒线程,而synchronized要么随机唤醒一个,要么唤醒全部.(接下来会演示)
- synchronized适合少量代码的同步问题,lock适合大量代码的同步问题
注意事项:
synchronized锁的是对象或者class 如果是new 或者this锁的就是对象而如果是static修饰的方法则锁的就是class.synchronized实现同步的基础:java中的每一个对象都可以作为锁。这也是为什么wait()方法是Object类的方法的原因,因为锁是对象层次的.
LOL上分的步骤
假设我们认为LOL上分有如下几个步骤,每个步骤有一个线程进行执行
步骤一: 开始匹配
步骤二:选择英雄
步骤三:开始游戏
步骤四:结束游戏
显然这几个步骤不能混合,一定要按照顺序进行执行
public class LOL {
public static void main(String[] args) {
PlayGame playGame = new PlayGame();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
playGame.match();
}
} , "A").start();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
playGame.select();
}
} , "B").start();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
playGame.start();
}
} , "C").start();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
playGame.end();
}
} , "D").start();
}
}
class PlayGame{
private Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
Condition condition4 = lock.newCondition();
/**
* num = 1 开始匹配 2 选择英雄 3 开始游戏 4 结束游戏
*/
private int num = 1;
/**
* 开始匹配
*/
public void match(){
lock.lock();
try {
while(num != 1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"开始匹配");
num = 2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 选择英雄
*/
public void select(){
lock.lock();
try {
while(num != 2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"选择英雄");
num = 3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 开始游戏
*/
public void start(){
lock.lock();
try {
while(num != 3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"开始游戏");
num = 4;
condition4.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 结束游戏
*/
public void end(){
lock.lock();
try {
while(num != 4){
condition4.await();
}
System.out.println(Thread.currentThread().getName()+"结束游戏");
num = 1;
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
线程之间的通信之生产者和消费者
老版的生产者和消费者是通过wait()和notyAll()实现的
public class PC {
public static void main(String[] args) {
Restaurant restaurant = new Restaurant();
/**
* 10个生产者
*/
for (int i = 0; i < 20; i++) {
new Thread(() ->{
try {
restaurant.p();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
for (int i = 0; i < 20; i++) {
new Thread(() ->{
try {
restaurant.c();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
class Restaurant{
/**
* 用来记录餐厅内有多少人
*/
private int num = 0;
public synchronized void p() throws Exception {
while(num == 20){
//生产者休息
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"当前生产了第"+num+"个");
this.notifyAll();
}
public synchronized void c() throws Exception {
while(num <=0){
//消费者休息
this.wait();
}
System.out.println(Thread.currentThread().getName()+"当前消费了第"+(num--)+"个"+"剩余"+num);
this.notifyAll();
}
}
新版生产者和消费者问题
通过lock锁替换synchronized ,通过condition的await()替换wait(),通过singal()替换notifyAll()
//测试代码和上面相同
class NewRestaurant{
/**
* 用来记录餐厅内有多少人
*/
private int num = 0;
private Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
public void p() throws Exception {
lock.lock();
try {
while(num == 20){
//生产者休息
condition1.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"当前生产了第"+num+"个");
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public synchronized void c() throws Exception {
lock.lock();
try {
while(num == 0){
//消费者休息
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"当前消费了第"+(num--)+"个"+"剩余"+num);
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
/*
Thread-0当前生产了第1个
Thread-8当前生产了第2个
Thread-1当前生产了第3个
Thread-2当前生产了第4个
Thread-3当前生产了第5个
Thread-13当前生产了第6个
Thread-4当前生产了第7个
Thread-17当前生产了第8个
Thread-7当前生产了第9个
Thread-5当前生产了第10个
Thread-6当前生产了第11个
Thread-9当前生产了第12个
Thread-10当前生产了第13个
Thread-11当前生产了第14个
Thread-12当前生产了第15个
Thread-15当前生产了第16个
Thread-14当前生产了第17个
Thread-16当前生产了第18个
Thread-18当前生产了第19个
Thread-19当前生产了第20个
Thread-20当前消费了第20个剩余19
Thread-22当前消费了第19个剩余18
Thread-21当前消费了第18个剩余17
Thread-24当前消费了第17个剩余16
Thread-23当前消费了第16个剩余15
Thread-26当前消费了第15个剩余14
Thread-28当前消费了第14个剩余13
Thread-30当前消费了第13个剩余12
Thread-25当前消费了第12个剩余11
Thread-27当前消费了第11个剩余10
Thread-29当前消费了第10个剩余9
Thread-31当前消费了第9个剩余8
Thread-32当前消费了第8个剩余7
Thread-33当前消费了第7个剩余6
Thread-34当前消费了第6个剩余5
Thread-36当前消费了第5个剩余4
Thread-38当前消费了第4个剩余3
Thread-35当前消费了第3个剩余2
Thread-39当前消费了第2个剩余1
Thread-37当前消费了第1个剩余0
*/
玩一下读写锁
首先说一下读写锁的用处?或者说为什么会有这个东西。我们加锁的目的是为了防止我们并发修改资源时,出现问题,但是如果我们不修改呢,我们只是单纯读取这个文件,那就应该支持多个线程同时去读取,因为读取的话,不会对资源造成改变,但是如果要修改的话,就必须要保证同一时刻只能有一个线程进行修改。
为满足上述问题也就有了我们的读写锁。
public class ReadWriteLockTest {
public static void main(String[] args) {
MapCache mapCache = new MapCache();
for (int i = 0; i < 10; i++) {
final String temp = i+"";
new Thread(() -> {
mapCache.addMap(temp , temp);
}).start();
}
for (int i1 = 0; i1 < 5; i1++) {
final String temp = i1+"";
new Thread(() ->{
mapCache.read(temp);
}).start();
}
}
}
class MapCache{
private Map<String , String> map = new HashMap<>();
private Lock lock = new ReentrantLock();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void addMap(String key , String value){
readWriteLock.writeLock().lock();
try {
map.put(key , value);
System.out.println(Thread.currentThread().getName()+"添加了"+key+"-->"+value);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void read(String key){
readWriteLock.readLock().lock();
try {
String value = map.get(key);
System.out.println(Thread.currentThread().getName()+"获取了"+value);
} catch (Exception e) {
} finally {
readWriteLock.readLock().unlock();
}
}
}
/*
测试结果如下:
Thread-0添加了0-->0
Thread-1添加了1-->1
Thread-2添加了2-->2
Thread-7添加了7-->7
Thread-3添加了3-->3
Thread-9添加了9-->9
Thread-4添加了4-->4
Thread-5添加了5-->5
Thread-6添加了6-->6
Thread-8添加了8-->8
Thread-10获取了0
Thread-13获取了3
Thread-12获取了2
Thread-14获取了4
Thread-11获取了1
*/
线程池
阻塞队列
谈这之前必须要先说一下阻塞队列
还是和以前一样,阻塞队列是用来干什么的?
阻塞队列是一个队列从上面的图我们不难看出来,他实现了Queue接口。
它的数结构图如下:
当队列为空时,从队列里取出元素就会阻塞
当队列满时,向队列里添加元素就会阻塞
接口架构图
经常使用的有:
ArrayBlockingQueue: 由数组结构组成的阻塞队列
LinkedBlockingQueue: 由链表结构组成的阻塞队列
常见API
处理方式 | 抛出异常 | 返回特殊值(T/F) | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入方式 | add(e) | offer(e) | put(e) | offer(e , time , unit) |
删除方式 | remove() | poll() | take() | poll(time , unit) |
抛出异常: 当不能往队列里面添加元素时,或者无法移除元素时会抛出异常
返回特殊值:当添加失败时会返回false成功则是true,删除元素成功时会返回元素,失败会返回null
一直阻塞: 当添加或者删除不能进行时,线程会一直等待直到队列,
超时退出: 当发生阻塞时,如果在规定的时间内队列仍不可用,线程则会退出
代码测试:
/**
* 创建一个阻塞队列 这里我选择了ArrayBlockingQueue
* 构造函数的参数是必须的 因为我们要只带队列最多可以存放几个元素
*/
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
/**
* add()和remove()测试
*/
arrayBlockingQueue.add(1);
arrayBlockingQueue.add(2);
arrayBlockingQueue.add(3);
arrayBlockingQueue.add(4);
/*
Exception in thread "main" java.lang.IllegalStateException: Queue full
at java.util.AbstractQueue.add(AbstractQueue.java:98)
at java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:312)
at com.sias.demo.juc.ArrayBlockingQueueTest.main(ArrayBlockingQueueTest.java:22)
*/
arrayBlockingQueue.remove();
arrayBlockingQueue.remove();
arrayBlockingQueue.remove();
arrayBlockingQueue.remove();
/*
Exception in thread "main" java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at com.sias.demo.juc.ArrayBlockingQueueTest.main(ArrayBlockingQueueTest.java:26)
*/
/*
*offer()方法和poll()方法测试 返回特殊值
*/
System.out.println(arrayBlockingQueue.offer(1));
System.out.println(arrayBlockingQueue.offer(2));
System.out.println(arrayBlockingQueue.offer(3));
System.out.println(arrayBlockingQueue.offer(4));
System.out.println("------------");
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
/*
true
true
true
false
------------
1
2
3
null
*/
/*
put()和take()方法测试 会一直阻塞
*/
arrayBlockingQueue.put(1);
arrayBlockingQueue.put(1);
arrayBlockingQueue.put(1);
arrayBlockingQueue.put(1);
arrayBlockingQueue.take();
arrayBlockingQueue.take();
arrayBlockingQueue.take();
arrayBlockingQueue.take();
/*
测试结果如下图
*/
/*
offer(e , time , unit)和poll(time,unit)
*/
arrayBlockingQueue.offer(1 , 2L, TimeUnit.SECONDS);
arrayBlockingQueue.offer(1 , 2L, TimeUnit.SECONDS);
arrayBlockingQueue.offer(1 , 2L, TimeUnit.SECONDS);
arrayBlockingQueue.offer(1 , 2L, TimeUnit.SECONDS);
arrayBlockingQueue.poll(2L , TimeUnit.SECONDS);
arrayBlockingQueue.poll(2L , TimeUnit.SECONDS);
arrayBlockingQueue.poll(2L , TimeUnit.SECONDS);
arrayBlockingQueue.poll(2L , TimeUnit.SECONDS);
/*
Process finished with exit code 0
*/
线程池
线程池的优点:
1. 提高处理速度:当任务来到时不需要再创建线程就可以直接执行
2. 降低资源消耗: 通过重复利用已创建的线程来减少线程的创建和销毁
3. 提高线程的可管理型
线程池的结构图
谈到线程池就不得不提到线程池的三大方法 七大参数 四中拒绝策略
所谓三大方法也就是创建线程池的三大方法:
-
Executors.newSingleThreadScheduledExecutor();
创建一个只含有一个线程池
-
Executors.newFixedThreadPool(5);
创建含有固定数量的线程池,上述就是含有5个线程池
- Executors.newCachedThreadPool();
创建线程数量可以扩展的线程池,遇强则强当需要的线程多的时候,线程池就会创建更多的线程放在线城池中
使用方法和创建线程是说到的使用线程池创建一样,这里就不做演示了。
通过查看API我们发现这三种方法最后都调用了ThreadPoolExecutor
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: 空闲线程的保留时间
TimeUnit unit: 保留时间的单位
TimeUnit.DAYS;//天
TimeUnit.HOURS;//小时
TimeUnit.MINUTES;//分
TimeUnit.SECONDS;//秒
TimeUnit.MILLISECONDS;//毫秒
TimeUnit.MICROSECONDS;//微秒
TimeUnit.NANOSECONDS;//纳秒
workQueue: 阻塞队列,用来存放排队等待的线程
threadFactory: 线程工厂,用来创建线程,一般默认即可
handler: 拒绝策略,线程达到最大线程数时再有任务进来就要触发拒绝策略了
ThreadPoolExecutor.AbortPolicy() //丢弃任务并且抛出异常
ThreadPoolExecutor.DiscardOldestPolicy()//丢弃队列最前面的任务,并且常识之星
ThreadPoolExecutor.DiscardPolicy() //也是丢弃任务但不抛出异常
ThreadPoolExecutor.CallerRunsPolicy()//有线程的调用者处理返任务
ThreadPoolExecutor 的工作原理
用银行办理业务来说明七个参数:
假设银行有两个窗口对外办理业务,对应的就是**核心线程数目,当有一个人来银行办理业务,会立即让其去一个窗口进行办理,再来一个人会让其去另一个窗口进行办理,如果这是再来一个人,这个人就会坐在银行大厅内的休息区进行等待,休息区对应的就是队列,如果再有人进来办理业务,会继续坐在休息区等待,直到休息区坐满了,在有人来就会有不同的==拒绝策略==,银行内最多进来几个人就是最大的核心线程数**
这么多线程池我们应该怎么选择?
我们应该使用ThreadPoolExecutor来创建线程,可能你会有疑问,既然JDK已经给你提供了三种创建线程池的方法,那我们为什么不用呢?我们可以点开源码,
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
我们会发现上面三个方法都是使用的Integer.MAX_VALUE作为最大核心线程数,这样的话可能会在队列中堆积大量的请求从而导致OOM。其实这一点在阿里巴巴开发手册上也有规定。
volatile(待更新)
三大特性:
1. 可见性
2. 不保证原子性
3. 禁止指令重排