JUC包详解(部分源码解读)
- atomic包
- locks包
- 集合
- 工具
atomic包
CAS算法
了解atomic包之前,首先需要了解的一个基本概念是CAS。CAS即Compare And Swap,就是比较并交换,这个算法的核心思想简而言之就是如果一个值,没有被别人改过,就原子性的去修改它。
这种做法是为了解决数据读写的非原子操作的问题,如执行i++操作,这个操作从程序员的角度上来看,可能会误以为这是一步操作,但是其实这个操作不是一个原子操作,可以看作是两步操作,1 读取i的值,2 将i的值赋为读取值加一。第一步和第二步操作中如果插入了一步赋值操作,那么第二步操作就会覆盖掉这个赋值操作,这个问题可以从下面的代码中看出来。
public class Mytest {
public static int t = 0;
public static void main(String[] args) throws InterruptedException {
for (int i =0;i<20;i++){
new Thread(new Test()).start();
}
Thread.sleep(100);
System.out.println(t);
}
public static void incr(){
t++;
}
}
class Test implements Runnable{
@Override
public void run() {
for (int i =0;i<100000;i++){
Mytest.incr();
}
}
}
而CAS算法可以很好的解决这个问题的同时,避免使用加锁的方式。我们都知道线程在用户态和核心态的转换会带来很大的一部分性能开销。CAS包括三个操作数,内存位置,预期值,更新值。以上面i++为例,当内存位置的值与预期值相等,则说明第一步和第二部操作中没有插入任何赋值操作,因此我们就可以更新值。而如果预期值和内存位置的值不一样,就说明被其他线程修改了这个值,因此CAS操作失败,这时我们可以选择放弃修改或者重新进行一次新的CAS操作。
简而言之,CAS就是一次有可能失败的原子赋值操作,是JUC包的基石之一。理解了这个,我们再来看看atomic包的内容。
AtomicInteger
atomic有一系列的原子操作类如AtomicBoolean,AtomicLong、AtomicInteger等,我们这里就以AtomicInteger为例。我们将上述代码使用AtomicInteger来进行累加操作。
public class Mytest {
public static AtomicInteger t = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
for (int i =0;i<20;i++){
new Thread(new Test()).start();
}
Thread.sleep(100);
System.out.println(t);
}
public static void incr(){
t.incrementAndGet();
}
}
class Test implements Runnable{
@Override
public void run() {
for (int i =0;i<100000;i++){
Mytest.incr();
}
}
}
代码修改后就完美的达到了我们想要的效果。这主要得益于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;
}
这段代码的核心逻辑就是如果与预期值相等,则为这个值增加var4,在incrementAndGet方法中var4就是1,这是一个自旋的方法直到自增成功。AtomicInteger包中还有许多其他的方法,如:compareAndSet、getAndDecrement、getAndAdd等,其底层原理都是通过CAS实现的。
AtomicReference和AtomicInteger是一样的,不过AtomicReference是处理引用类型的类。
ABA问题以及AtomicStampedReference
ABA问题是CAS过程中可能存在的问题,由于CAS是判断内存中的值是否与预期值相同的操作,那如果这个值原来是A,中途被其他线程改成了B,后来又改回来了A,那么普通的CAS算法就会认为这个值是没有其他线程改动过的,在某些特殊场景中,这可能会存在问题。因此引入了AtomicStampedReference类来解决这种ABA问题。这个问题的解决思路就是为这个值带上一个“版本号”,在CAS的过程中,不单只对比值是否相同,也要对比“版本号”是否相同,如果版本号和值都相同,则可以任务没被其他线程改过。
locks包
AQS
AbstractQueuedSynchronizer简称AQS,即抽象队列同步器,我们可以简单的理解成一个排队规则。是JUC包的基石之一。
举个例子:有一个餐馆,只允许一个人用餐。当用餐时间到了,很多人都希望来这家餐馆吃饭,但是餐馆同一时间只允许一个人吃饭,因此第一个到了并且成功点菜的人,就获得了吃饭的权利,没轮到的人就只能在餐馆外排队等候。在里面吃饭的人,可以点多个菜,只要把菜全部吃完了,那就说明服务员可以让下一个人进来点菜吃饭了。这就是AQS的大概思路。
AQS的核心包括一个volatile修饰的int型变量state(对应上述例子中点了多少个菜),以及一个基于双向链表的等待队列(餐馆外排的队)。在这个例子中,用餐的人就是java程序中的一个个线程。如果一个线程成功更新了state则说明它获取到了这个资源,其他线程就进入队列等待。下面我们结合ReentrantLock来看AQS是怎么运作的。
ReentrantLock
ReentrantLock的底层就是通过AQS实现的。ReentrantLock内部维护了一个AQS的子类,下面看一下ReentrantLock的加锁代码。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
首先是通过CAS去更新State的值,如果更新成功,则将当前线程设置为独占线程,否则执行acquire方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在这个方法中首先会执行tryAcquire尝试获取资源,这个方法的底层代码逻辑如下
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()) {//如果资源被占有了,则判断是否是当前线程占有的,如果是,则state增加入参值。
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;//如果不满足以上两种情况,尝试获取值失败,返回失败。
}
tryAcquire尝试获取资源成功则acquire直接退出,失败就执行addWaiter方法将线程添加到队列尾部并标注为独占模式,然后执行acquireQueued使得线程线程在队列中等待获取资源,等待过程中如果发生过中断,则返回true,否则返回false。如果过程中发生了中断,则在获取资源后执行自我中断,下面是这部分的代码。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {//CAS尝试加入队尾,成功则返回
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))//队列为空初始化队列
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {//尝试加入队尾
t.next = node;
return t;
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;//此标志位用于判断是否被中断过
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {//是否是头节点并尝试获取资源
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//这一步会让线程休息等待被唤醒
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)这个方法总结一下就是线程进入队尾,然后进入一个等待状态等待被唤醒,唤醒之后会判断是否队头指向自己并尝试获取资源,获取资源成功则设置当前节点为队头,如果没拿到,就重新进入等待状态,重复这个流程。
经过上述代码分析,我们回过头看这个加锁的过程,其实就是一句话:尝试获取state资源,如果获取不到,则把这个线程加入等待队列等待。
下面我们再看看ReentrantLock的释放锁的代码。
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {//尝试释放锁
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//成功释放锁则唤醒等待的线程
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//要判断是否释放为0了,ReentrantLock是重入锁可能出现多次加锁,所以要对应多次解锁
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;//锁已经释放返回true,还没释放返回false
}
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {//这里有个逻辑,队头元素不一定是当前节点的下一个元素,因为ReentrantLock有非公平锁的模式
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;//找到队头
}
if (s != null)
LockSupport.unpark(s.thread);//这个方法唤醒等待的线程
}
释放锁流程就是也是一句话:尝试减少state的值,如果state归零则唤醒队头的等待线程。
因此,看到这里,我们大概就了解了ReentrantLock是怎么通过AQS来实现一个加锁和解锁的过程。
ReentrantReadWriteLock
AQS还有另外一种共享模式,ReentrantReadWriteLock的读锁底层就是通过AQS的共享模式处理的。
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();//获取当前线程和state状态
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)//是否有写锁,读写锁都是通过state存储的,写锁用低16位存储,读锁用高16位存储,exclusiveCount方法就是通过位运算获取写锁,有写锁则判断线程是否为当前线程。
return -1;
int r = sharedCount(c);//没有写锁,获取读锁数量
if (!readerShouldBlock() &&//是否需要等待,公平锁需要等待
r < MAX_COUNT &&//读锁数量不能超过最大值
compareAndSetState(c, c + SHARED_UNIT)) {//cas尝试获取锁
if (r == 0) {//当前线程是第一个尝试获得读锁的线程
firstReader = current;
firstReaderHoldCount = 1;//线程占用资源数量
} else if (firstReader == current) {//当前线程是第一个获取读锁的线程,而且读锁重入
firstReaderHoldCount++;//线程占用资源数量
} else {//读锁数量不为0,也不是相同线程
HoldCounter rh = cachedHoldCounter;//这是个计数器
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();//当前线程计数器,这个readHolds的底层是ThreadLocal
else if (rh.count == 0)
readHolds.set(rh);//计数器加入到readHolds
rh.count++;//计数器自增
}
return 1;
}
return fullTryAcquireShared(current);
}
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();//获取state
if (exclusiveCount(c) != 0) {//同上,判断写锁
if (getExclusiveOwnerThread() != current)//有写锁,而且写锁持有线程不为当前线程
return -1;
} else if (readerShouldBlock()) {//无写锁,且需要阻塞
if (firstReader == current) {//是否第一个获得读锁的线程
} else {//不是第一个获得的线程
if (rh == null) {
rh = cachedHoldCounter;//获取缓存计数器
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)//超过最大值
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {cas尝试获取读锁
if (sharedCount(c) == 0) {//第一个获取读锁的线程
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {//读锁重入
firstReaderHoldCount++;
} else {//获取计数器并自增
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;//逻辑同上,当前线程计数器需要自增
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;//中断标志位
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
读写锁与单纯的排他锁主要区别在于读锁的共享性,在读写锁实现中保证读锁能够共享的其中一个机制就在于,如果一个读锁等待节点被唤醒后其会继续唤醒拍在当前唤醒节点之后的SHARED模式等待节点。所以如果当前锁是读锁状态则等待节点第一个节点一定是写锁等待节点。
CountDownLatch、CyclicBarrier、Semaphore
CountDownLatch与Semaphore的底层是通过实现AQS的子类Sync来实现并发控制,而CyclicBarrier持有一个ReentrantLock对象来实现障碍。
CountDownLatch是一个计数器,当计数器为减少为零时,唤醒所有等待计数器的线程。
CountDownLatch核心逻辑
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);//AQS的子类
}
Sync(int count) {
setState(count);//通过AQS的state属性,设置计数器的值
}
public void countDown() {
sync.releaseShared(1);//countDown其实就是尝试释放共享锁,减一操作,当state计数为0,所有因为await方法进入等待的方法将被唤醒。
}
Semaphore可以控制一段代码最多同时被多少线程执行,当线程数量大于最大信号量时,则其他线程等待获取令牌。
Semaphore核心逻辑
public Semaphore(int permits) {
sync = new NonfairSync(permits);//单参构造默认非公平锁
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);//boolean参数可选是否为非公平锁
}
Sync(int permits) {
setState(permits);//公平锁和fei公平锁的底层都是通过维护AQS的state属性来控制并发
}
public final void acquireSharedInterruptibly(int arg)//Semaphore申请令牌的底层实现方法
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//尝试减少state,如果state为零则说明需要加入队列排队
doAcquireSharedInterruptibly(arg);//进队排队等待分配令牌
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {//自旋
int available = getState();//获取当前state
int remaining = available - acquires;//尝试获取令牌
if (remaining < 0 ||//为负数就说明剩余令牌数不足,要排队等待了
compareAndSetState(available, remaining))//cas设置成功则返回剩余令牌数
return remaining;
}
}
CyclicBarrier作用与CountDownLatch相同,但是CyclicBarrier是可以重复使用的,CountDownLatch则不行。
CyclicBarrier核心逻辑
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;//记录计数器的值,每一次计数器重置都设置为该值
this.count = parties;//计数器的值,每次调用减少到0则全部线程释放
this.barrierCommand = barrierAction;//一个可运行的Runnable实现
}
private int dowait(boolean timed, long nanos)//这个方法是CyclicBarrier的await方法的底层实现
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();//通过ReentrantLock加锁
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();//判断是否断开
if (Thread.interrupted()) {//判断是否被中断
breakBarrier();
throw new InterruptedException();
}
int index = --count;//自减计数器
if (index == 0) { // 计数器为零说明一轮技术结束 栅栏可以放行
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)//传入的Runnable实现不为空,则执行runnable对象的run方法
command.run();
ranAction = true;
nextGeneration();//进入下一次循环,唤醒所有等待线程
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await();//trip底层是Condition,执行它的await方法
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)//不相等说明进入了新的一次循环因此返回
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
private void nextGeneration() {//这里就是进入下一次循环的方法
// signal completion of last generation
trip.signalAll();//唤醒所有等待的对象
// set up next generation
count = parties;//重置计数器
generation = new Generation();//生成新的循环对象
}
集合
ConcurrentHashMap
ConcurrentHashMap是一个线程安全的hashMap,其底层是通过CAS加synchronized来保证线程安全的,这点区别于1.7中的Segment分段锁保证线程安全的机制。ConcurrentHashMap的底层是通过数组+链表/红黑树对数据进行存取,当链表长度大于8时,链表就会转为红黑树,加快了数据查找效率(O(N)–>O(log(N)))。
ConcurrentHashMap的默认大小为16,每次扩容为当前两倍。下面主要分析ConcurrentHashMap的添加元素方法。
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());//计算hash值,位运算(h ^ (h >>> 16)) & HASH_BITS;
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();//数组还未初始化,则数组进行初始化
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break;// 如果当前哈希值对应位置没有值,则尝试CAS获取值,成功则跳出循环
}
else if ((fh = f.hash) == MOVED)//正在进行扩容操作,
tab = helpTransfer(tab, f);//协助扩容
else {
V oldVal = null;
synchronized (f) {//上锁
if (tabAt(tab, i) == f) {//判断是否已经被其他线程更新值了
if (fh >= 0) {//fh>0说明不是树节点 是链表
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {//遍历链表,
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {//hash值相同,key相等,说明key存在
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;//设置为新值
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {//节点切换到下一节点,如果为空说明链表没找到key
pred.next = new Node<K,V>(hash, key,//添加新节点,存KV
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {//如果是树,就添加红黑树节点
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)//超过8转化为红黑树
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);//检查是否需要扩容
return null;
}
CopyOnWriteArrayList
CopyOnWriteArrayList是一个线程安全的数组,下面看看它的添加元素和删除元素的方法。
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();
}
}
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();//获取数组
int len = elements.length;//获取长度
E oldValue = get(elements, index);
int numMoved = len - index - 1;//计算index后面有多少元素
if (numMoved == 0)//index后面没有元素,说明index为最后一个元素,则直接复制到最后一个元素之前即可
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);//将index之前的元素复制到新数组
System.arraycopy(elements, index + 1, newElements, index,//将index之后的元素复制到新数组
numMoved);
setArray(newElements);//设置新数组
}
return oldValue;
} finally {
lock.unlock();
}
}
从代码中可以看出CopyOnWriteArrayList虽然是一个线程安全的数组,但是每一次增删数据都要进行数组复制,因此适合写少读多的场景。
ArrayBlockingQueue
这是个基于数组的阻塞队列,下面我们来看看它的入队出队(阻塞方式)方法。
public void put(E e) throws InterruptedException {
checkNotNull(e);//判断是否为空
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//可中断锁
try {
while (count == items.length)//队列满了
notFull.await();//notFull是一个Condition,调用await阻塞等待至队列内有空间可以放入数据。
enqueue(e);//入队
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;//将数据存入队列尾
if (++putIndex == items.length)
putIndex = 0;//队列尾已经到达数组边界,则设置为数组头
count++;//队列计数器增加
notEmpty.signal();//新加入了数据,所以数组不为空,唤醒为空时阻塞取的等待线程。
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();//队列为空时等待至队列不为空
return dequeue();//取数据
} finally {
lock.unlock();
}
}
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];//获取数据
items[takeIndex] = null;//清除已经获取的数据位置
if (++takeIndex == items.length)//队列头移动
takeIndex = 0;//队列头到达边界则重置为数组头
count--;//计数器减一
if (itrs != null)
itrs.elementDequeued();//通知迭代器
notFull.signal();//队列满时,取出数据则唤醒阻塞线程
return x;
}
多线程工具
Executors
Executors内部定义了多种线程池,下面介绍常用的四种(不推荐)。
public static ExecutorService newFixedThreadPool(int nThreads) {//这是个固定线程数量的线程池
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {//这是个单例线程池,池内只有一个线程运行
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {//这是个线程自动增长的线程池,空闲线程最多存活六十秒
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());//这是个没有任何容量的队列,每次插入都会等待对应的删除操作
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);//这是个可以执行延时任务和定时任务的线程池
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());//这里也是相当于new ThreadPoolExecutor
}
以上线程池传入的LinkedBlockingQueue是一个基于链表的队列,这是个无界队列,可能导致会出现OOM异常,因此建议手动创建线程池,下面介绍手动创建线程池的参数
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;
}