一、读写锁ReadWriteLock
- ReadWriteLock是接口被 ReentrantReadWriteLock 类实现: public class ReentrantReadWriteLock extends Object implements ReadWriteLock, Serializable
- ReadWriteLock维护一对关联的locks ,一个用于只读操作,一个用于写入。 read lock可以由多个阅读器线程同时进行,只要没有作者。 write lock是独家的。简单理解就是 可以多个线程同时读,只能有一个线程同时写。
- 原则上读写问题使用synchronized和Lock可以解决,但是粒度较读写锁大。
- 写锁相当于独占锁,读锁相当于共享锁 相比互斥锁
- 相比互斥锁,更加细粒度。
- 从理论上讲,通过使用读写锁允许的并发性增加将导致性能改进超过使用互斥锁。
- 读写锁是否会提高使用互斥锁的性能取决于数据被读取的频率与被修改的频率相比,读取和写入操作的持续时间以及数据的争用 。即是,将尝试同时读取或写入数据的线程数。 例如,最初填充数据的集合,然后经常被修改的频繁搜索(例如某种目录)是使用读写锁的理想候选。 然而,如果更新变得频繁,那么数据的大部分时间将被专门锁定,并且并发性增加很少。不建议使用读写锁。简单理解就是 经常被修改的读取的数据,建议使用读写锁,对于不长变动而且 并发很少的情况,不建议使用读写锁。
1.自定义缓存不加锁情况举栗
package study_rw_lock;
import java.util.HashMap;
import java.util.Map;
/**
* ReadWriteLock
*/
public class Demo1_ReadWriteLock {
public static void main(String[] args) {
MyCache cache = new MyCache();
// 模拟4个线程写入缓存
for (int i = 1; i <= 4; i++) {
final int temp = i;
new Thread(()->{
cache.save(temp +"",temp+"");
},String.valueOf(i)).start();
}
// 模拟4个线程读取缓存
for (int i = 1; i <= 4; i++) {
final int temp = i;
new Thread(()->{
cache.get(temp +"");
},String.valueOf(i)).start();
}
}
}
/**
* 模拟缓存 写入、读取
*/
class MyCache {
private volatile Map<String,String> cache = new HashMap<>();
public void save(String key,String value){
System.out.println(Thread.currentThread().getName() + "===》开始写入数据");
cache.put(key,value);
System.out.println(Thread.currentThread().getName() + "===》写入成功了 " + key + "---" + value);
}
public void get(String key){
System.out.println(Thread.currentThread().getName() + "===》开始获取数据");
String result = cache.get(key);
System.out.println(Thread.currentThread().getName() + "===》获取成功了 " + result);
}
}
输出:
1===》开始写入数据 ======> 正确的逻辑应该是写入然后读取,但是不加锁的时候,读取之前被 多个线程同时更新了缓存
4===》开始写入数据
4===》写入成功了 4---4
3===》开始写入数据
2===》开始写入数据
2===》写入成功了 2---2
3===》写入成功了 3---3
1===》写入成功了 1---1
1===》开始获取数据
2===》开始获取数据
2===》获取成功了 2
3===》开始获取数据
3===》获取成功了 3
4===》开始获取数据
1===》获取成功了 1
4===》获取成功了 4
2.加上读写锁举栗
- // 读写锁对象
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); - // 加入写锁,保证只能同时有一个线程来写,// 释放写锁
reentrantReadWriteLock.writeLock().lock(); reentrantReadWriteLock.writeLock().unlock(); - // 加上读锁,保证读的时候不能有其他线程写 // 释放读锁
reentrantReadWriteLock.readLock().lock(); reentrantReadWriteLock.readLock().unlock();
package study_rw_lock;
import jdk.nashorn.internal.ir.CallNode;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock
*/
public class Demo1_ReadWriteLock {
public static void main(String[] args) {
MyCacheWriteReadLock cache = new MyCacheWriteReadLock();
// 模拟4个线程写入缓存
for (int i = 1; i <= 4; i++) {
final int temp = i;
new Thread(()->{
cache.save(temp +"",temp+"");
},String.valueOf(i)).start();
}
// 模拟4个线程读取缓存
for (int i = 1; i <= 4; i++) {
final int temp = i;
new Thread(()->{
cache.get(temp +"");
},String.valueOf(i)).start();
}
}
}
/**
* 模拟缓存 加上读写锁 写入、读取
*/
class MyCacheWriteReadLock {
private volatile Map<String,String> cache = new HashMap<>();
// 读写锁对象
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
public void save(String key,String value){
// 加入写锁,保证只能同时有一个线程来写
reentrantReadWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "===》开始写入数据");
cache.put(key,value);
System.out.println(Thread.currentThread().getName() + "===》写入成功了 " + key + "---" + value);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放写锁
reentrantReadWriteLock.writeLock().unlock();
}
}
public void get(String key){
// 加上读锁,保证读的时候不能有其他线程写
reentrantReadWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "===》开始获取数据");
String result = cache.get(key);
System.out.println(Thread.currentThread().getName() + "===》获取成功了 " + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放读锁
reentrantReadWriteLock.readLock().unlock();
}
}
}
输出:
2===》开始写入数据
2===》写入成功了 2---2
3===》开始写入数据
3===》写入成功了 3---3
1===》开始写入数据
1===》写入成功了 1---1
4===》开始写入数据
4===》写入成功了 4---4
1===》开始获取数据
1===》获取成功了 1
4===》开始获取数据
3===》开始获取数据
3===》获取成功了 3
2===》开始获取数据
2===》获取成功了 2
4===》获取成功了 4
3.加上Lock锁和synchronized情况举栗
package study_rw_lock;
import jdk.nashorn.internal.ir.CallNode;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock
*/
public class Demo1_ReadWriteLock {
public static void main(String[] args) {
// MyCacheWriteReadLock cache = new MyCacheWriteReadLock();
MyCache_Synchronized cache = new MyCache_Synchronized();
// 模拟4个线程写入缓存
for (int i = 1; i <= 4; i++) {
final int temp = i;
new Thread(()->{
cache.save(temp +"",temp+"");
},String.valueOf(i)).start();
}
// 模拟4个线程读取缓存
for (int i = 1; i <= 4; i++) {
final int temp = i;
new Thread(()->{
cache.get(temp +"");
},String.valueOf(i)).start();
}
}
}
/**
* 1. 模拟缓存 不加锁 写入、读取
*/
class MyCache {
private volatile Map<String,String> cache = new HashMap<>();
public void save(String key,String value){
System.out.println(Thread.currentThread().getName() + "===》开始写入数据");
cache.put(key,value);
System.out.println(Thread.currentThread().getName() + "===》写入成功了 " + key + "---" + value);
}
public void get(String key){
System.out.println(Thread.currentThread().getName() + "===》开始获取数据");
String result = cache.get(key);
System.out.println(Thread.currentThread().getName() + "===》获取成功了 " + result);
}
}
/**
* 3. 模拟缓存 加读写锁 写入、读取
*/
class MyCacheWriteReadLock {
private volatile Map<String,String> cache = new HashMap<>();
// 读写锁对象
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
public void save(String key,String value){
// 加入写锁,保证只能同时有一个线程来写
reentrantReadWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "===》开始写入数据");
cache.put(key,value);
System.out.println(Thread.currentThread().getName() + "===》写入成功了 " + key + "---" + value);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放写锁
reentrantReadWriteLock.writeLock().unlock();
}
}
public void get(String key){
// 加上读锁,保证读的时候不能有其他线程写
reentrantReadWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "===》开始获取数据");
String result = cache.get(key);
System.out.println(Thread.currentThread().getName() + "===》获取成功了 " + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放读锁
reentrantReadWriteLock.readLock().unlock();
}
}
}
/**
* 4. 模拟缓存 加synchronized 写入、读取
*/
class MyCache_Synchronized {
private volatile Map<String,String> cache = new HashMap<>();
public synchronized void save(String key,String value){
System.out.println(Thread.currentThread().getName() + "===》开始写入数据");
cache.put(key,value);
System.out.println(Thread.currentThread().getName() + "===》写入成功了 " + key + "---" + value);
}
public synchronized void get(String key){
System.out.println(Thread.currentThread().getName() + "===》开始获取数据");
String result = cache.get(key);
System.out.println(Thread.currentThread().getName() + "===》获取成功了 " + result);
}
}
/**
* 3. 模拟缓存 加Lock锁 写入、读取
*/
class MyCacheLock {
private volatile Map<String,String> cache = new HashMap<>();
Lock lock = new ReentrantLock();
public void save(String key,String value){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "===》开始写入数据");
cache.put(key,value);
System.out.println(Thread.currentThread().getName() + "===》写入成功了 " + key + "---" + value);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void get(String key){
System.out.println(Thread.currentThread().getName() + "===》开始获取数据");
String result = cache.get(key);
System.out.println(Thread.currentThread().getName() + "===》获取成功了 " + result);
}
}
二、阻塞队列BlockingQueue
- 队列是FIFO先进先出的一种数据结构
- 阻塞指两种状态
- 入队:如果队列此时是满的,需要阻塞等待
- 取出:如果队列是空的,需要阻塞等待生产
- BlockingQueue是一个接口
- 父接口:All Superinterfaces Collection , Iterable , Queue
- 实现接口:All Known Subinterfaces:BlockingDeque , TransferQueue
- 实现子类: ArrayBlockingQueue , DelayQueue , LinkedBlockingDeque , LinkedBlockingQueue , LinkedTransferQueue , PriorityBlockingQueue , SynchronousQueue
- A Queue另外支持在检索元素时等待队列变为非空的操作,并且在存储元素时等待队列中的空间变得可用。也就是支持阻塞。
- 基于典型的生产者 - 消费者场景。 请注意, BlockingQueue可以安全地与多个生产者和多个消费者一起使用。
- 方法有四种形式,也就是阻塞的处理方式 具有不同的操作方式,不能立即满足,但可能在将来的某个时间点满足:
- 一个抛出异常,
- 第二个返回一个特殊值( null或false ,具体取决于操作),
- 第三个程序将无限期地阻止当前线程,直到操作成功为止,
- 第四个程序块在放弃之前只有给定的最大时限。 什么情况下使用阻塞队列
- 多线程并发情景
- 线程池维护线程
1.使用阻塞队列
- 使用时需要指定容量
- 常用队列操作:入队、出队、判断队首元素
- 4组API使用结果 :抛异常、返回特殊值、阻塞等待、超时等待 操作 | 抛出异常 | 返回特殊值 | 阻塞等待 | 超时等待 | | ------ | --------- | ------- | ------ | ------------------ | | 入队 | add() | offer() | put() | offer(value,时间,单位) | | 出队 | remove() | poll() | take() | poll(时间,单位) | | 获取队首元素 | element() | peek() | - | -
package study_blockingqueue;
import java.util.Collection;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
//test1();
//test2();
//test3();
test4();
}
/**
* add、remove 不合理情况 抛异常
*/
public static void test1(){
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
queue.add("xiao");
queue.add("si");
queue.add("xiaosi");
System.out.println(queue);
//queue.add("队列已满,入队会抛异常");IllegalStateException
System.out.println(queue.remove());
// 获取队首元素
System.out.println(queue.element());
System.out.println(queue.remove());
System.out.println(queue.remove());
// System.out.println(queue.element()); 队列为空的时候,获取队首元素抛异常 NoSuchElementException
System.out.println(queue);
//System.out.println(queue.remove()); 队列已空,再次出队会抛异常 NoSuchElementException
}
/**
* offer、poll 入队失败返回false,出队失败返回 null
*
*/
public static void test2(){
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
queue.offer("xiao");
queue.offer("si");
queue.offer("xiaosi");
System.out.println(queue.offer("队列已满,入队会返回false"));
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll()); // 队列为空,出队返回null
}
/**
* put 、take 阻塞等待
*
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
queue.put("xiao");
queue.put("si");
queue.put("xiaosi");
queue.put("队列已满,入队会一直阻塞等待");
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take()); // 队列为空,出队一直阻塞等待
}
/**
* put 、take 超时等待,超时自动结束
*
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
queue.put("xiao");
queue.put("si");
queue.put("xiaosi");
queue.offer("队列已满,入队会超时等待",3, TimeUnit.SECONDS);
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.poll(1,TimeUnit.SECONDS)); // 队列为空,超时等待,过时间自动结束
}
}
三、同步队列SynchronousQueue
- SynchronionousQueue是一个类
- 实现的父接口:All Implemented Interfaces: Serializable , Iterable , Collection , BlockingQueue , Queue
- A blocking queue其中每个插入操作必须等待另一个线程相应的删除操作,反之亦然。
- 同步队列没有任何内部容量,甚至没有一个容量。 简单理解就是入队一个元素必须出队之后其他元素才能接着入队
- 你不能peek在同步队列,因为一个元素,当您尝试删除它才存在;
- 您无法插入元素(使用任何方法),除非另有线程正在尝试删除它;
- 你不能迭代,因为没有什么可以迭代。
- 队列的头部是第一个排队的插入线程尝试添加到队列中的元素; 如果没有这样排队的线程,那么没有元素可用于删除,并且poll()将返回null 。 为了其他Collection方法(例如contains )的目的, SynchronousQueue充当空集合。
- 此队列不允许null元素。
package study_synchronionousqueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class Demo1 {
public static void main(String[] args) {
BlockingQueue<String> queue = new SynchronousQueue<>();
// 模拟一个线程 像同步队列插入三个元素
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " 线程入队了 === 1");
queue.put("1");
System.out.println(Thread.currentThread().getName() + " 线程入队了 === 2");
queue.put("2");
System.out.println(Thread.currentThread().getName() + " 线程入队了 === 3");
queue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
// 模拟一个相乘 取出元素
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " 线程出队了 === >" + queue.take() );
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " 线程出队了 === >" + queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " 线程出队了 === >" + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
输出:
T1 线程入队了 === 1
T2 线程出队了 === >1
T1 线程入队了 === 2
T2 线程出队了 === >2
T1 线程入队了 === 3
T2 线程出队了 === >3
四、线程池
池化技术
- 程序的运行会占用系统的资源,使用池化技术可以优化资源的使用
- 线程池、连接池、内存池、对象池等 线程池优点
- 线程复用、控制最大并发数、管理线程
- 降低资源的消耗
- 提高响应速度
- 方便管理
1.线程池的三大方法-使用Executors创建线程池
- 实现父接口:All Implemented Interfaces: Executor , ExecutorService
- 已知直接子类:ScheduledThreadPoolExecutor
- Executors创建线程池实例常用的三个方法
-
- newSingleThreadExecutor()创建一个使用从无界队列运行的单个工作线程的执行程序。
- newCachedThreadPool()创建一个根据需要创建新线程的线程池核心线程数为0,全部线程可回收,在可用时将重新使用以前构造的线程。
- newFixedThreadPool(int nThreads)创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
- newScheduThreadPool()创建一个定时任务线程池
- 线程池关闭的方法pool.shutdown()
package study_threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public class Demo1 {
public static void main(String[] args) {
ExecutorService pool1 = Executors.newSingleThreadExecutor();
ExecutorService pool2 = Executors.newFixedThreadPool(6);
ExecutorService pool3 = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 10; i++) {
pool1.execute(()->{
System.out.println(Thread.currentThread().getName() );
});
}
} finally {
// 关闭线程池
pool1.shutdown();
}
try {
for (int i = 0; i < 10; i++) {
pool2.execute(()->{
System.out.println(Thread.currentThread().getName() );
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pool2.shutdown();
}
try {
for (int i = 0; i < 10; i++) {
pool3.execute(()->{
System.out.println(Thread.currentThread().getName() );
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pool3.shutdown();
}
}
}
输出:
pool-1-thread-1======> 线程池 pool1 的线程,可以发现只有一个
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-2-thread-1 =======> 线程池pool2 的线程,可以发现最多有设置的 6 个线程
pool-2-thread-2
pool-2-thread-4
pool-2-thread-3
pool-2-thread-3
pool-2-thread-1
pool-2-thread-1
pool-2-thread-5
pool-2-thread-3
pool-2-thread-6
pool-3-thread-1 =========> 线程池 pool3 的线程,根据电脑性能情况,遇强则强
pool-3-thread-2
pool-3-thread-3
pool-3-thread-3
pool-3-thread-4
pool-3-thread-2
pool-3-thread-4
pool-3-thread-5
pool-3-thread-6
pool-3-thread-1
2.线程池的七大参数-源码
==================== newSingleThreadExecutor() =====================
/**
* Creates an Executor that uses a single worker thread operating
* off an unbounded queue. (Note however that if this single
* thread terminates due to a failure during execution prior to
* shutdown, a new one will take its place if needed to execute
* subsequent tasks.) Tasks are guaranteed to execute
* sequentially, and no more than one task will be active at any
* given time. Unlike the otherwise equivalent
* {@code newFixedThreadPool(1)} the returned executor is
* guaranteed not to be reconfigurable to use additional threads.
*
创建一个 Executor,它使用单个工作线程在无界队列中运行。
(但是请注意,如果这个单线程在关闭之前由于执行失败而终止,如果需要执行后续任务,一个新线程将取代它。)
保证任务按顺序执行,并且不会超过一个任务处于活动状态在任何给定的时间。
与其他等效的 {@code newFixedThreadPool(1)} 不同,返回的执行器保证不可重新配置以使用其他线程。
* @return the newly created single-threaded Executor
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
===================== newFixedThreadPool(int nThreads) =======================
/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue. At any point, at most
* {@code nThreads} threads will be active processing tasks.
* If additional tasks are submitted when all threads are active,
* they will wait in the queue until a thread is available.
* If any thread terminates due to a failure during execution
* prior to shutdown, a new one will take its place if needed to
* execute subsequent tasks. The threads in the pool will exist
* until it is explicitly {@link ExecutorService#shutdown shutdown}.
*
*创建一个线程池,该线程池重用固定数量的线程在共享的无界队列中运行。
在任何时候,最多 {@code nThreads} 个线程将是活动的处理任务。
如果在所有线程都处于活动状态时提交了其他任务,它们将在队列中等待,直到有线程可用。
如果任何线程在关闭前的执行过程中因失败而终止,则在需要执行后续任务时,将有一个新线程取而代之。
池中的线程将一直存在,直到明确 {@link ExecutorServiceshutdown shutdown}。
* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
=================== newCachedThreadPool() =================
/**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available. These pools will typically improve the performance
* of programs that execute many short-lived asynchronous tasks.
* Calls to {@code execute} will reuse previously constructed
* threads if available. If no existing thread is available, a new
* thread will be created and added to the pool. Threads that have
* not been used for sixty seconds are terminated and removed from
* the cache. Thus, a pool that remains idle for long enough will
* not consume any resources. Note that pools with similar
* properties but different details (for example, timeout parameters)
* may be created using {@link ThreadPoolExecutor} constructors.
*
创建一个线程池,根据需要创建新线程,但在可用时将重用先前构造的线程。
这些池通常会提高执行许多短期异步任务的程序的性能。
如果可用,调用 {@code execute} 将重用先前构造的线程。
如果没有可用的现有线程,则会创建一个新线程并将其添加到池中。
60 秒内未使用的线程将被终止并从缓存中删除。
因此,保持空闲足够长时间的池不会消耗任何资源。
请注意,可以使用 {@link ThreadPoolExecutor} 构造函数创建具有相似属性但不同细节(例如,超时参数)
的池。
*
* @return the newly created thread pool
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- 发现三种方式本质是
new ThreadPoolExecutor()
使用给定的初始参数创建一个新的 {@code ThreadPoolExecutor}。
@param corePoolSize 池中要保留的线程数,即使它们处于空闲状态,除非设置了 {@code allowCoreThreadTimeOut}
@param maximumPoolSize 池中允许的最大线程数
@param keepAliveTime 当线程数较大时与核心相比,这是多余空闲线程在终止之前等待新任务的最长时间。
@param unit {@code keepAliveTime} 参数的时间单位
@param workQueue 用于在执行任务之前保存任务的队列。该队列将仅保存由 {@code execute} 方法提交的 {@code Runnable}
任务(等待队列)。
@param threadFactory 执行程序创建新线程时使用的工厂
@param handler 执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量 @throws IllegalArgumentException 如果以下
情况之一成立: {@code corePoolSize < 0}
{@code keepAliveTime < 0}
{@code maximumPoolSize <= 0}
{@code maximumPoolSize < corePoolSize}
@throws NullPointerException if
{@code workQueue} 或 {@code threadFactory}或 {@code handler} 为空
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
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;
}
七大参数
- 核心线程数 :@param corePoolSize 池中要保留的线程数,即使它们处于空闲状态,除非设置了 {@code allowCoreThreadTimeOut}
- 最大线程数:@param maximumPoolSize 池中允许的最大线程数
- 空闲存活时间:@param keepAliveTime 当线程数较大时与核心相比,这是多余空闲线程在终止之前等待新任务的最长时间。
- 超时单位:@param unit {@code keepAliveTime} 参数的时间单位
- 阻塞队列: @param workQueue 用于在执行任务之前保存任务的队列。该队列将仅保存由 {@code execute} 方法提交的 {@code Runnable} 任务。
- 线程工厂 :@param threadFactory 执行程序创建新线程时使用的工厂
- 拒接策略:@param handler 执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量 @throws IllegalArgumentException 如果以下情况之一成立: {@code corePoolSize < 0} {@code keepAliveTime < 0} {@code maximumPoolSize <= 0} {@code maximumPoolSize < corePoolSize} @throws NullPointerException if {@code workQueue} 或 {@code threadFactory}或 {@code handler} 为空
3.原生new ThreadPoolExecutor 创建线程池
- 阿里开发手册也说明,尽量使用原生的方式创建线程池,而不是使用Executors封装类。可以更好地理解底层实现,避免资源浪费的情况。
- 使用 new ThreadPoolExecutor()创建一个想线程池,其中
- 线程工厂查看源码,默认的是 Executors.defaultThreadFactory()
- 拒绝策略查看源码,默认的是 new ThreadPoolExecutor.AbortPolicy(),拒绝策略会抛出异常 RejectedExecutionException
=============== 默认线程工厂 ===================
/**
* Returns a default thread factory used to create new threads.
* This factory creates all new threads used by an Executor in the
* same {@link ThreadGroup}. If there is a {@link
* java.lang.SecurityManager}, it uses the group of {@link
* System#getSecurityManager}, else the group of the thread
* invoking this {@code defaultThreadFactory} method. Each new
* thread is created as a non-daemon thread with priority set to
* the smaller of {@code Thread.NORM_PRIORITY} and the maximum
* priority permitted in the thread group. New threads have names
* accessible via {@link Thread#getName} of
* <em>pool-N-thread-M</em>, where <em>N</em> is the sequence
* number of this factory, and <em>M</em> is the sequence number
* of the thread created by this factory.
* @return a thread factory
*
* 返回用于创建新线程的默认线程工厂。
* 该工厂在同一个 {@link ThreadGroup} 中创建由 Executor 使用的所有新线程。
* 如果有 {@link java.lang.SecurityManager},则使用 {@link SystemgetSecurityManager} 组,否则
使用调用此 {@code defaultThreadFactory} 方法的线程组。
* 每个新线程都被创建为非守护线程,其优先级设置为 {@code Thread.NORM_PRIORITY} 和线程组中允许的最大优先级中的
较小者。
* 新线程的名称可以通过 <em>pool-N-thread-M<em> 的 {@link ThreadgetName} 访问,
* 其中 <em>N<em> 是这个工厂的序列号,<em>M<em>是这个工厂创建的线程的序列号。
*/
public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
=================== 默认的拒接策略 ==============
/**
* The default rejected execution handler
*/
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
package study_threadpool;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo2_ThreadPoolExecutor {
public static void main(String[] args) {
/**
* 使用原生的方式创建 线程池
*/
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
5,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try {
/*
核心线程 2
最大线程 5
阻塞队列 3
====测试1====
执行任务 3
输出:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1,只开启了核心线程1,2
====测试2====
执行任务 5
输出:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2,只开启了核心线程1,2
====测试3====
执行任务 6
输出:
pool-1-thread-2
pool-1-thread-3
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3,除了开启了核心线程,还另外开了一个线程3
====测试4====
执行任务 9
输出:
pool-1-thread-3
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-1
pool-1-thread-5,超出最大线程数8,(阻塞队列 + 最大线程数),共 8 个输出,最后一个任务被拒绝,抛出异常.RejectedExecutionException
*/
for (int i = 1; i <= 9; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() );
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
4.四种拒绝策略
RejectedExecutionHandler 是拒绝策略的接口,有四个实现类
- AbortPolicy : 默认拒绝策略,抛出异常
- DiscardPolicy:不会抛出异常,会丢掉任务
- DiscardOldestPolicy:不会抛出异常,会和最早的线程尝试竞争资源,竞争不一定会成功,失败就丢调任务
- CallerRunsPolicy:从哪个线程来的,哪个线程处理,即main线程处理
5.最大线程数应该怎么设置?
- CPU密集型:和本机核心数保持一致,CPU性能最高。
获取CPU核心数:System.out.println("查看本机核心数:" + Runtime.getRuntime().availableProcessors()); - IO密集型:根据程序中大型IO耗时线程,保证大于等于。