Java 线程与线程池
线程的状态
-
NEW, 新建状态, 线程被创建出来, 但尚未启动时的线程状态
-
RUNNABLE, 就绪状态, 表示可以运行的线程状态, 它可能正在运行, 或者是在排队等待操作系统给它分配 CPU 资源
-
BLOCKED, 阻塞等待锁的线程状态, 表示处于阻塞状态的线程正在等待监视器锁, 比如等待执行
synchronized代码块或者使用synchronized标记的方法 -
WAITING, 等待状态, 一个处于等待状态的线程正在等待另一个线程执行某个特定的动作, 比如, 一个线程调用了
Object.wait()方法, 那它就在等待另一个线程调用Object.notify()或Object.notifyAll()方法 -
TIMED_WAITING, 计时等待状态, 和 WAITING 类似, 它只是多了超时时间, 比如调用了有超时时间设置的方法
Object.wait(long timeout)和Thread.join(long timeout)等这些方法时, 它才会进入此状态 -
TERMINATED, 终止状态, 表示线程已经执行完成
关于 Object.wait/notify
相关代码
public class WaitNotifyCase {
public static void main(String[] args) {
final Object lock = new Object();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread A is waiting to get lock");
synchronized (lock) {
try {
System.out.println("thread A get lock");
TimeUnit.SECONDS.sleep(1);
System.out.println("thread A do wait method");
lock.wait();
System.out.println("wait end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread B is waiting to get lock");
synchronized (lock) {
System.out.println("thread B get lock");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notify();
System.out.println("thread B do notify method");
}
}
}).start();
}
}
执行结果
thread A is waiting to get lock
thread A get lock
thread B is waiting to get lock
thread A do wait method
thread B get lock
thread B do notify method
wait end
疑问
- 进入
wait/notify方法之前, 为什么要获取synchronized锁? - 线程 A 获取了
synchronized锁, 执行wait方法并挂起, 线程 B 又如何再次获取锁?
分析
synchronized代码块通过javap生成的字节码中包含monitorenter和monitorexit指令, 执行monitorenter指令可以获取对象的monitor, 在wait()接口注释中有标明The current thread must own this object's monitor, 所以通过synchronized该线程持有了对象的monitor的情况下才能调用对象的wait()方法wait()接口注释中还提到调用wait()后该线程会释放持有的monitor进入等待状态直到被唤醒, 被唤醒的线程还要等到能重新持有monitor才会继续执行- 线程状态变化:
- 调用
wait(): RUNNABLE -> WAITING - 调用
notify:- WAITING -> BLOCKED -> RUNNABLE
- WAITING -> RUNNABLE
- 具体看 JVM 实现和策略配置
- 调用
深入: 什么是 monitor
-
在
HotSpot虚拟机中 (1.7 版本),monitor采用ObjectMonitor实现-
ObjectMonitor() { _header = NULL; _count = 0; // 用来记录该线程获取锁的次数 _waiters = 0, _recursions = 0; // 锁的重入次数 _object = NULL; // 对应的对象 _owner = NULL; // 指向持有 ObjectMonitor 对象的线程 _WaitSet = NULL; // 处于 WAITING 状态的线程, 会被加入到 _WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; // 竞争锁的线程都会先通过互斥同步或 CAS 操作进入 cxq, 队首的对象会进入到 EntryList 中, 进行 tryLock 操作 FreeNext = NULL ; _EntryList = NULL ; // 处于 BLOCKED 状态的线程, 会被加入到 _EntryList _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; }
-
-
每个线程都有两个
ObjectMonitor对象列表, 分别为free和used列表, 如果当前free列表为空, 线程将向全局global ListLock请求分配ObjectMonitor -
ObjectMonitor对象中有两个队列:_WaitSet和_EntryList, 用来保存ObjectWaiter对象列表;_owner指向获得ObjectMonitor对象的线程 -
每个等待锁的线程都会被封装成
ObjectWaiter对象ObjectWaiter对象是双向链表结构, 保存了_thread(当前线程)以及当前的状态TState等数据
-
ObjectMonitor获得锁是通过void ATTR enter(TRAPS);方法
void ATTR ObjectMonitor::enter(TRAPS) {
Thread * const Self = THREAD ;
void * cur ;
// 通过 CAS 尝试把 monitor 的 _owner 设置为当前线程
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
// 获取锁失败
if (cur == NULL) { assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert OwnerIsThread == 1
return ;
}
// 如果旧值和当前线程一样, 说明当前线程已经持有锁, 此次为重入, _recursions 自增即可
if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions ++ ;
return ;
}
// 如果当前线程是第一次进入该 monitor, 设置 _recursions 为 1, _owner 为当前线程
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
// Commute owner from a thread-specific on-stack BasicLockObject address to
// a full-fledged "Thread *".
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
// 省略部分代码
......
// 在调用系统的同步操作之前,先尝试自旋获得锁
if (Knob_SpinEarly && TrySpin (Self) > 0) {
...
// 自旋的过程中获得了锁,则直接返回
Self->_Stalled = 0 ;
return ;
}
......
// 通过自旋执行 ObjectMonitor::EnterI 方法等待锁的释放
for (;;) {
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition()
// or java_suspend_self()
// 将当前线程插入到 cxq 队列, 挂起等待重新尝试获取锁
EnterI (THREAD) ;
if (!ExitSuspendEquivalent(jt)) break ;
// We have acquired the contended monitor, but while we were
// waiting another thread suspended us. We don't want to enter
// the monitor while suspended because that would surprise the
// thread that suspended us.
//
_recursions = 0 ;
_succ = NULL ;
exit (Self) ;
jt->java_suspend_self();
}
}
ObjectMonitor释放锁是通过void ATTR exit(TRAPS);方法
void ATTR ObjectMonitor::exit(TRAPS) {
Thread * Self = THREAD ;
// 如果当前线程不是 Monitor 的所有者
if (THREAD != _owner) {
if (THREAD->is_lock_owned((address) _owner)) {
// Transmute _owner from a BasicLock pointer to a Thread address.
// We don't need to hold _mutex for this transition.
// Non-null to Non-null is safe as long as all readers can
// tolerate either flavor.
assert (_recursions == 0, "invariant") ;
_owner = THREAD ;
_recursions = 0 ;
OwnerIsThread = 1 ;
} else {
// NOTE: we need to handle unbalanced monitor enter/exit
// in native code by throwing an exception.
// TODO: Throw an IllegalMonitorStateException ?
TEVENT (Exit - Throw IMSX) ;
assert(false, "Non-balanced monitor enter/exit!");
if (false) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
return;
}
}
// 如果 _recursions 次数不为 0.自减
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
TEVENT (Inflated exit - recursive) ;
return ;
}
// 省略部分代码, 根据不同的策略(由 QMode 指定), 从 _cxq 或 EntryList 中获取头节点, 通过 ObjectMonitor::ExitEpilog 方法唤醒该节点封装的线程, 唤醒操作最终由 unpark 完成。
-
lock.wait()方法最终通过ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);实现:- 将当前线程封装成
ObjectWaiter对象node - 通过
ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中 - 通过
ObjectMonitor::exit方法释放当前的ObjectMonitor对象, 这样其它竞争线程就可以获取该ObjectMonitor对象 - 最终底层的
park方法会挂起线程
- 将当前线程封装成
-
lock.notify()方法最终通过ObjectMonitor的void notify(TRAPS)实现:- 如果当前
_WaitSet为空, 即没有正在等待的线程, 则直接返回 - 通过
ObjectMonitor::DequeueWaiter方法, 获取_WaitSet列表中的第一个ObjectWaiter节点 - 根据不同的策略, 将取出来的
ObjectWaiter节点加入到_EntryList或则通过Atomic::cmpxchg_ptr指令进行自旋操作_cxq
- 如果当前
关于中断
Java 中线程间是协作式,而非抢占式
- 调用一个线程的
interrupt()方法中断一个线程,并不是强行关闭这个线程,只是通知线程停止,将线程的中断标志位置为true,线程是否中断,由线程本身决定, 线程可以进行停止前的释放资源, 完成必要的处理任务
相关方法
- 在线程内可通过
isInterrupted()判断中断并进行相应处理 - 另一个静态方法
Thread.interrupted()返回当前线程的中断状态,同时重置中断标志位为false
如何正确停止线程
-
中断会唤醒阻塞的线程, 并且大部分都会抛出
InterruptedException -
如果方法里如果抛出中断异常
InterruptedException,则线程的中断标志位会被复位成false -
实际开发中的两种最佳实践
-
传递中断
-
对于底层的方法, 在方法的签名上标注异常 (
throws InterruptedException) -
抛出异常,而异常的真正处理,应该交给调用它的那个函数
-
因为标注了异常, 调用者必须对
InterruptedException异常进行处理
-
-
恢复中断
- 在底层方法中
catch处理异常 - 处理完成后手动调用
Thread.currentThread().interrupt()恢复中断
- 在底层方法中
-
相关问题
1. BLOCKED(阻塞等待)和 WAITING(等待)有什么区别?
- 状态形成的调用方法不同
- BLOCKED 可以理解为当前线程还处于活跃状态, 只是在阻塞等待其他线程使用完某个锁资源
- WAITING 则是因为自身调用了
Object.wait()或着是Thread.join()又或者是LockSupport.park()而进入等待状态, 只能等待其他线程执行某个特定的动作才能被继续唤醒, 比如当线程因为调用了Object.wait()而进入 WAITING 状态之后, 则需要等待另一个线程执行Object.notify()或Object.notifyAll()才能被唤醒
2. start() 方法和 run() 方法有什么区别?
-
public synchronized void start() { // 状态验证, 不等于 NEW 的状态会抛出异常 if (threadStatus != 0) throw new IllegalThreadStateException(); // 通知线程组, 此线程即将启动 group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { // 通知线程组, 此线程启动失败 group.threadStartFailed(this); } } catch (Throwable ignore) { // 不处理任何异常, 如果 start0 抛出异常, 则它将被传递到调用堆栈上 } } }start()方法属于Thread自身的方法, 并且使用了synchronized来保证线程安全
-
run()方法为Runnable的抽象方法, 重写的run()方法其实就是此线程要执行的业务方法 -
调用
start()方法是另起线程来运行run()方法中的内容
3. 线程的优先级有什么用?该如何设置?
-
在
Thread源码中和线程优先级相关的属性有 3 个-
// 线程可以拥有的最小优先级 public final static int MIN_PRIORITY = 1; // 线程默认优先级 public final static int NORM_PRIORITY = 5; // 线程可以拥有的最大优先级 public final static int MAX_PRIORITY = 10
-
-
线程的优先级可以理解为线程抢占 CPU 时间片的概率, 优先级越高的线程优先执行的概率就越大, 但并不能保证优先级高的线程一定先执行
-
在程序中我们可以通过
Thread.setPriority()来设置优先级-
public final void setPriority(int newPriority) { ThreadGroup g; // 检查当前线程是否有权限修改优先级 checkAccess(); // 先验证优先级的合理性 if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { // 优先级如果超过线程组的最高优先级, 则把优先级设置为线程组的最高优先级 if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } }
-
4. 线程的常用方法有哪些?
-
sleep
Thread.sleep()让线程进入到TIMED_WAITING状态, 并停止占用 CPU 资源, 但是不释放持有的monitor, 直到规定事件后再执行, 休眠期间如果被中断, 会抛出异常并清除中断状态TimeUnit.SECONDS.sleep()比Thread.sleep()多了非负数判断
-
join
-
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } } -
本质是用
wait()实现, 这里wait()在循环中调用, 是为了避免可能发生的 虚假唤醒 (spurious wakeup) 情况 -
JVM 的
Thread执行完毕会自动执行一次notifyAll(), 所以不建议在程序中对Thread对象调用wait/notify, 可能会造成干扰
-
-
yield
-
A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint. -
状态依旧是
RUNNABLE, 不保证释放 CPU 资源 -
Thread.sleep(0)可以重新触发 CPU 的竞争, 而yield不一定
-
5. 被弃用的方法有哪些? 为什么被弃用?
- suspend
- 使线程暂停, 但不会释放
monitor, 所以容易造成死锁
- 使线程暂停, 但不会释放
- resume
- 恢复通过调用
suspend()方法而停止运行的线程
- 恢复通过调用
- stop
- 强制停止当前线程, 会释放该线程所持有对象的
monitor, 因而可能造成这些对象处于不一致的状态 - 而且这个方法造成的
ThreadDeath异常不像其他的检查期异常一样被捕获
- 强制停止当前线程, 会释放该线程所持有对象的
ThreadLocal
-
ThreadLocal是线程的内部存储类,可以在指定线程内存储数据。只有指定线程可以得到存储数据 -
每个线程都有一个
ThreadLocalMap的实例对象,并且通过ThreadLocal管理ThreadLocalMap-
class Thread implements Runnable { ...... /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; ...... }
-
应用场景
-
每个线程需要有自己单独的实例
-
实例需要在多个方法中共享,但不希望被多线程共享
-
并非必须使用
ThreadLocal,其它方式完全可以实现同样的效果,只是ThreadLocal使得实现更简洁
相关变量和定义
public class ThreadLocal<T> {
// 每个 ThreadLocal 实例都有唯一的 hashCode
private final int threadLocalHashCode = nextHashCode();
// threadLocalHashCode 值是从 0 开始每次累加 HASH_INCREMENT
private static AtomicInteger nextHashCode =
new AtomicInteger();
// 该魔法值是取自 2^32 * 黄金分割数, 为的是能好的散列
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
// 通过继承 ThreadLocal 并实现 initialValue() 方法可以实现初始值
protected T initialValue() {
return null;
}
static class ThreadLocalMap {
// map 中的 key 为弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
// 存放的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// Entry 数组初始容量
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
// Entry 数组扩容阈值
private int threshold; // Default to 0
// 阈值为容量的 2/3
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
......
}
}
-
关于为什么
HASH_INCREMENT = 0x61c88647可阅读 从 ThreadLocal 的实现看散列算法 -
set()
// ThreadLocal
public void set(T value) {
// 获取当前线程实例
Thread t = Thread.currentThread();
// 从当前线程实例中取出 map
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 未初始化 map, 初始化 map
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
// 存在哈希冲突的情况时需要后移一位继续判断
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// key 匹配,直接设置值
if (k == key) {
e.value = value;
return;
}
if (k == null) {
// 原先该位置的 key 已被回收, 进行清除工作并替换
replaceStaleEntry(key, value, i);
return;
}
}
// 当 i 位置为空或哈希冲突后移找到空位置时, 插入数组
tab[i] = new Entry(key, value);
int sz = ++size;
// 从 i 位置进行快速清理,如果清理成功并且 sz 大于阈值则触发扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// 向前寻找最前一个 key 已被回收的 Entry
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// 考虑哈希冲突的情况, 向后查找要插入的 key 是否存在
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) {
// 存在 key, 覆盖旧 value
e.value = value;
// 原先冲突的 key 已经被回收, 所以将该 Entry 与其交换
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// slotToExpunge == staleSlot 代表 staleSlot 前没有 key 已被回收的 Entry
if (slotToExpunge == staleSlot)
// 因为交换过位置, 则 slotToExpunge 也要改为 i
slotToExpunge = i;
// 从 slotToExpunge 位置开始清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
// staleSlot 前没有 key 已被回收的 Entry, 则 slotToExpunge 从这个位置开始
slotToExpunge = i;
}
// 找不到存在的 key 则新建一个 Entry 插入
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 如果 slotToExpunge != staleSlot 说明还存在其他 key 已被回收的 Entry, 进行清理
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 将 value 和对应位置置空, 使其能够被回收
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 继续向后清理, 直到 tab[i] 为 null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 将哈希冲突的 Entry 往前移填补清理后空出来的位置
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
// 返回 staleSlot 后下一个为 null 的位置
return i;
}
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
// 发现 key 为 null 则进行清理
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
// 每次 n 取半
// 这是权衡后的算法, 虽然不能全部扫描但能使清理工作能保持在 O(log2(n))
// 避免插入时会有 O(n) 的时间消耗
} while ( (n >>>= 1) != 0);
return removed;
}
private void rehash() {
// 清理
expungeStaleEntries();
// 清理完之后大小还是不够 3/4 阈值的话进行扩容
if (size >= threshold - threshold / 4)
resize();
}
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
// 从旧数组循环取出 Entry 放入新数组
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
// 遍历并调用 expungeStaleEntry(j) 清理 Stale Entry
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
get()
public T get() {
// 获取当前线程实例
Thread t = Thread.currentThread();
// 从当前线程实例中取出 map
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 未初始化 map, 初始化 map 并返回初始值
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
// 存在哈希冲突的情况, 无法直接根据坐标取到想要的 Entry
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 向后查找 key, 遇到 Stale Entry 则进行清理
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
关于内存泄露
ThreadLocal对象实际上存放在Thread实例中threadLocals成员的Entry数组里- 如果线程一直处于活跃状态, 则
ThreadLocal对象就算离开具体业务逻辑的作用域, 也因为threadLocals持有强引用而无法被回收- 所以
ThreadLocalMap已经将Entry设为弱引用, gc 会在ThreadLocal离开作用域后对其回收, 但是这是针对 key 的,Entry持有的value却不会被回收
- 所以
- 针对这种情况,
ThreadLocal中get()、set()、remove()这些方法中都存在清理threadLocals中 key 为null的逻辑, 起到了惰性删除释放内存的作用
InheritableThreadLocal
-
InheritableThreadLocal是为了解决在子线程中获取不到父线程ThreadLocal值的问题 -
Thread中也持有对应的inheritableThreadLocals, 并在init中有相关的初始化逻辑-
class Thread implements Runnable { /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ...... Thread parent = currentThread(); // 如果存在 inheritableThreadLocals 则传递给新建出来的子线程 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); ...... } ...... } public class ThreadLocal<T> { static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); } static class ThreadLocalMap { private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; // 逐一复制 parentMap 的记录 for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } } ...... } ...... }
-
-
重写了
ThreadLocal的三个方法public class InheritableThreadLocal<T> extends ThreadLocal<T> { protected T childValue(T parentValue) { return parentValue; } ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } }
线程池 ( ThreadPoolExecutor)
-
线程池是为了避免线程频繁的创建和销毁带来的性能消耗, 而建立的一种池化技术, 它是把已创建的线程放入“池”中, 当有任务来临时就可以重用已有的线程, 无需等待创建的过程, 这样就可以有效提高程序的响应速度
-
构造函数
-
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { ...... }-
corePoolSize表示线程池的常驻核心线程数- 如果设置为 0, 则表示在没有任何任务时, 销毁线程池
- 如果大于 0, 即使没有任务时也会保证线程池的线程数量等于此值
- 此值如果设置的比较小, 则会频繁的创建和销毁线程
- 如果设置的比较大, 则会浪费系统资源, 所以开发者需要根据自己的实际业务来调整此值
-
maximumPoolSize表示线程池在任务最多时, 最大可以创建的线程数- 此值必须大于 0, 也必须大于等于
corePoolSize, 此值只有在任务比较多, 且不能存放在任务队列时才会用到
- 此值必须大于 0, 也必须大于等于
-
keepAliveTime表示线程的存活时间- 当线程池空闲时并且超过了此时间, 多余的线程就会销毁, 直到线程池中的线程数量销毁的等于
corePoolSize为止 - 如果
maximumPoolSize等于corePoolSize, 那么线程池在空闲的时候也不会销毁任何线程
- 当线程池空闲时并且超过了此时间, 多余的线程就会销毁, 直到线程池中的线程数量销毁的等于
-
unit表示存活时间的单位, 它是配合keepAliveTime参数共同使用的 -
workQueue表示线程池执行的任务队列- 当线程池的所有线程都在处理任务时, 如果来了新任务就会缓存到此任务队列中排队等待执行
-
threadFactory表示线程的创建工厂, 此参数一般用的比较少, 我们通常在创建线程池时不指定此参数, 它会使用默认的线程创建工厂的方法来创建线程:-
// 默认的线程创建工厂, 需要实现 ThreadFactory 接口 static class DefaultThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } // 创建线程 public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (t.isDaemon()) t.setDaemon(false); // 创建一个非守护线程 if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); // 线程优先级设置为默认值 return t; } } -
我们也可以自定义一个线程工厂, 通过实现
ThreadFactory接口来完成, 这样就可以自定义线程的名称或线程执行的优先级了
-
-
RejectedExecutionHandler表示指定线程池的拒绝策略- 当线程池的任务已经在缓存队列
workQueue中存储满了之后, 并且不能创建新的线程来执行此任务时, 就会用到此拒绝策略, 它属于一种限流保护的机制
- 当线程池的任务已经在缓存队列
-
ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
-
用一个
AtomicInteger包装两个字段:- 高 3 位保存
runState, 低 29 位保存workerCount - 用一个变量去存储两个值, 可避免在做相关决策时, 出现不一致的情况, 不必为了维护两者的一致, 而占用锁资源
workerCount: 有效线程数runState: 线程池的运行状态- 定义
- RUNNING: 接受新任务并处理排队的任务
- SHUTDOWN: 拒绝接受新任务, 但是会处理还在排队的任务
- STOP: 拒绝接受新任务, 也不处理排队中任务, 并且会中断正在执行的任务
- TIDYING: 所有任务都已经停止,
workerCount为 0, 转换为状态 TIDYING 的线程将运行terminated()方法 - TERMINATED:
terminated()执行完毕
- 这些值之间的数字顺序很重要, 可以进行有序的比较
runState随着时间逐步增加, 但不一定达到每个状态, 过渡的顺序为:- RUNNING -> SHUTDOWN, 在调用
shutdown()时, 可能隐藏在finalize()中调用 - (RUNNING or SHUTDOWN) -> STOP, 在调用
shutdownNow()时 - SHUTDOWN -> TIDYING, 当队列和池子内的任务都为空时
- STOP -> TIDYING, 当池子内的任务为空时
- TIDYING -> TERMINATED, 当
terminated()执行完毕时
- RUNNING -> SHUTDOWN, 在调用
- 定义
- 高 3 位保存
线程池工作流程
通过 execute() 执行任务
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 当前工作的线程数小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// 创建新的线程执行此任务, 传入 true 以核心线程数作为判断阈值
if (addWorker(command, true))
return;
// 创建失败, 说明 ctl 有变化, 重新获取
c = ctl.get();
}
// 检查线程池是否处于运行状态, 如果是则把任务添加到队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次检查线程池是否处于运行状态, 防止在第一次校验通过后线程池关闭
// 如果是非运行状态, 则将刚加入队列的任务移除
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果线程池的线程数为 0 时(当 corePoolSize 设置为 0 时会发生)
else if (workerCountOf(recheck) == 0)
addWorker(null, false); // 新建线程执行任务, 传入 false 以最大线程数作为判断阈值
}
// 核心线程和队列都满了, 新建非核心线程执行
else if (!addWorker(command, false))
// 新建线程失败, 执行拒绝策略
reject(command);
}
addWorker(Runnable firstTask, boolean core)方法firstTask, 线程应首先运行的任务, 如果没有则可以设置为nullcore, 判断是否可以创建线程的阀值(最大值), 如果等于true则表示使用corePoolSize作为阀值,false则表示使用maximumPoolSize作为阀值
Worker
构造函数
-
private final class Worker extends AbstractQueuedSynchronizer implements Runnable { final Thread thread; // Worker 持有的线程 Runnable firstTask; // 初始化的任务, 可以为 null Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } ...... }
执行任务流程
继承 AQS 原因分析
Worker 是通过继承 AQS, 使用 AQS 来实现独占锁这个功能。不用可重入锁 ReentrantLock 而用 AQS, 为的就是实现不可重入的特性去反应线程现在的执行状态
-
Worker.lock方法一旦获取了独占锁, 表示当前线程正在执行任务中, 正在执行任务的线程不应该被中断 -
如果正在执行任务,则不应该中断线程
-
线程池在执行
shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程是否在执行任务, 如果是空闲状态则可以安全回收 -
之所以要不可重入, 是为了避免在
Worker中会调用到线程池interruptIdleWorkers, 像setCorePoolSize方法。如果使用ReentrantLock, 它是可重入的, 这样会导致该Worker自己被中断
-
此外, 在构造方法中执行了
setState(-1);, 把state变量设置为 -1, 是因为AQS默认的state是 0, 如果刚创建了一个Worker对象, 还没有执行任务时, 这时就不应该被中断:-
protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } -
tryAcquire方法是根据state是否是 0 来判断的, 所以,setState(-1);将state设置为 -1 是为了防止在执行任务前就中断了线程 -
在
runWorker方法中会先调用Worker对象的unlock方法将state设置为 0, 允许中断和Worker.lock
-
相关参数
// 用于操作 workers
private final ReentrantLock mainLock = new ReentrantLock();
// 持有线程的引用, 管理线程的生命周期
private final HashSet<Worker> workers = new HashSet<Worker>();
// 用于通知线程池终止完毕
private final Condition termination = mainLock.newCondition();
// 线程池曾经创建过的最大线程数量
private int largestPoolSize;
// 线程池已经执行的和未执行的任务总数
private long completedTaskCount;
- 为什么
workers不采用线程安全的集合 ?- 有许多复合的操作, 比如说将
worker添加到workers后还需要判断是否需要更新largestPoolSize等,workers只在获取到mainLock的情况下才会进行读写 mainLock也用于在中断线程的时候串行执行, 否则可能会并发进行线程中断, 引起不必要的中断高峰
- 有许多复合的操作, 比如说将
addWorker : 增加工作线程
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && // 线程池是否已停止
! (rs == SHUTDOWN && // 线程池是否正在停止
firstTask == null && ! workQueue.isEmpty()) // 线程是否用于执行剩余任务
)
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY || // 线程数是否超过容量
wc >= (core ? corePoolSize : maximumPoolSize)) // 是否超过判断的阀值
return false;
if (compareAndIncrementWorkerCount(c)) // CAS 尝试登记线程数
break retry; // 登记成功
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs) // 判断线程池状态运行过程中是否有改变
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); // 持有引用
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s; // 更新创建过的最大线程数
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); // 启动线程, 而线程的 run 方法就是执行 runWorker()
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
runWorker : 不断获取任务并执行
Worker被创建出来后, 就会不断地进行轮询, 然后获取任务去执行, 核心线程可以无限等待获取任务, 非核心线程要限时获取任务
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 获取第一个任务
Runnable task = w.firstTask;
w.firstTask = null;
// 允许中断
w.unlock(); // allow interrupts
// 是否因为异常退出循环
boolean completedAbruptly = true;
try {
// 如果task为空, 则通过getTask来获取任务
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
/**
* 确保只有在线程 stoping 时,才会被设置中断标示,否则清除中断标示
* 1、如果线程池状态 >= stop,且当前线程没有设置中断状态,wt.interrupt()
* 2、如果一开始判断线程池状态 < stop,但 Thread.interrupted() 为 true (调用的同时清除了中断标示),即线程已经被中断,再次判断线程池状态是否 >= stop
* 是,再次设置中断标示,wt.interrupt()
* 否,不做操作,清除中断标示后进行后续步骤
*/
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
// 标明是正常退出
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
getTask : 从任务队列获取任务
private Runnable getTask() {
// timeOut 表示上次从阻塞队列中取任务时是否超时
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
/*
* 1. 线程池已经 stop
* 2. 线程池处于 shutdown 并且队列为空
* 如果以上任何条件满足, 则将 workerCount 减 1 并返回 null
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
// timed 用于判断是否需要进行超时控制
// allowCoreThreadTimeOut 默认是 false, 也就是核心线程不允许进行超时
// wc > corePoolSize, 表示当前线程池中的线程数量大于核心线程数量
// 对于超过核心线程数量的这些线程, 需要进行超时控制
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
/*
* 1. 判断 wc > maximumPoolSize 是因为可能通过 setMaximumPoolSize 修改过 maximumPoolSize
* 2. timed && timedOut 如果为 true, 表示当前操作需要进行超时控制, 并且上次从阻塞队列中获取任务发生了超时
* 满足 1 或 2 并且如果有效线程数量大于 1 或者阻塞队列是空的, 那么尝试将 workerCount 减 1
* 判断 wc > 1 是防止在 allowCoreThreadTimeOut 为 true 或 corePoolSize 为 0 时无线程执行还在等待中的任务
* 如果减 1 失败, 则返回重试
*/
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
/*
* 根据 timed 来判断, 如果为 true, 则通过阻塞队列的 poll 方法进行超时控制
* 如果在 keepAliveTime 时间内没有获取到任务, 则返回 null
* 否则通过 take 方法, 如果这时队列为空, 则 take 方法会阻塞直到队列不为空。
*
*/
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 如果 r == null, 说明已经超时, timedOut 设置为 true
timedOut = true;
} catch (InterruptedException retry) {
// 如果获取任务时当前线程发生了中断, 则设置 timedOut 为 false 并返回循环重试
timedOut = false;
}
}
}
processWorkerExit : 线程回收
-
线程池中线程的销毁依赖
JVM的垃圾回收, 当线程池决定哪些线程需要回收时, 只需要将其引用消除即可 -
当
Worker无法获取到任务, 也就是获取的任务为空时, 循环会结束,Worker会主动消除自身在线程池内的引用 -
线程回收的工作在
processWorkerExit方法内完成-
private void processWorkerExit(Worker w, boolean completedAbruptly) { // 如果 completedAbruptly 值为 true, 则说明线程执行时出现了异常, 需要将 workerCount 减 1 // 如果线程执行时没有出现异常, 说明在 getTask() 方法中已经已经对 workerCount 进行了减 1 操作 if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 统计完成的任务数 completedTaskCount += w.completedTasks; // 从 workers 中移除, 也就表示着从线程池中移除了一个工作线程 workers.remove(w); } finally { mainLock.unlock(); } // 根据线程池状态进行判断是否结束线程池 tryTerminate(); int c = ctl.get(); /* * 当线程池是 RUNNING 或 SHUTDOWN 状态时, 如果 worker 是异常结束, 那么会直接 addWorker * 如果 allowCoreThreadTimeOut 为 true, 并且等待队列有任务, 至少保留一个 worker * 如果 allowCoreThreadTimeOut 为 false, workerCount 不少于 corePoolSize */ if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && ! workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } addWorker(null, false); } } -
事实上在这个方法中, 将线程引用移出线程池就已经结束了线程销毁的部分。但由于引起线程销毁的可能性有很多, 线程池还要判断是什么引发了这次销毁, 是否要改变线程池的现阶段状态, 是否要根据新状态, 重新分配线程
-
tryTerminate : 根据状态判断是否结束
final void tryTerminate() {
for (;;) {
int c = ctl.get();
/*
* 当前线程池的状态为以下几种情况时, 直接返回:
* 1. RUNNING, 因为还在运行中, 不能停止
* 2. TIDYING 或 TERMINATED, 说明正在或者已经在终止
* 3. SHUTDOWN 并且等待队列非空, 这时要执行完 workQueue 中的 task;
*/
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 到这个位置为以下情况之一
// 1. 线程池的状态为 SHUTDOWN 并且等待队列为空
// 2. 线程池的状态为 STOP
// 如果线程数量不为 0, 则中断一个空闲的工作线程, 并返回
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
// 到这个位置则说明线程数量为 0
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 尝试设置状态为 TIDYING, 如果成功则调用 terminated 方法
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// terminated 方法默认什么都不做, 留给子类实现
terminated();
} finally {
// terminated() 执行完毕, 设置状态为 TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
// 通知完成终止
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
// 没设置成功则继续 CAS 尝试
}
}
shutdown , shutdownNow : 关闭线程池
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 安全策略判断
checkShutdownAccess();
// 切换状态为 SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断空闲线程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试结束线程池
tryTerminate();
}
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 设置状态为 STOP
advanceRunState(STOP);
// 中断所有工作线程
interruptWorkers();
// 取出队列中没有被执行的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
interruptIdleWorkers, interruptWorkers : 中断工作线程
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// 通过 w.tryLock 判断是否为空闲
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
......
void interruptIfStarted() {
Thread t;
// 如果线程已经启动, 中断线程
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
awaitTermination : 等待线程池完成终止
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
if (nanos <= 0)
return false;
// 通过 termination 进行等待
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
相关问题
1. ThreadPoolExecutor 的执行方法有几种?它们有什么区别?
-
execute()VSsubmit()-
都是用来执行线程池任务, 它们最主要的区别是
submit()方法可以接收线程池执行的返回值, 而execute()不能接收返回值 -
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(20)); // execute 使用 executor.execute(new Runnable() { @Override public void run() { System.out.println("Hello, execute."); } }); // submit 使用 Future<String> future = executor.submit(new Callable<String>() { @Override public String call() throws Exception { System.out.println("Hello, submit."); return "Success"; } }); System.out.println(future.get()); -
execute()方法属于Executor接口的方法, 而submit()方法则是属于ExecutorService接口的方法 -
在
submit()中处理的任务如果抛出异常, 只有在调用返回的Future对象get方法时才会抛出
-
2. 拒绝策略的分类有哪些? 如何自定义拒绝策略?
-
自带的拒绝策略有 4 种:
- AbortPolicy, 终止策略, 线程池会抛出异常并终止执行, 它是默认的拒绝策略
- CallerRunsPolicy, 把任务交给当前线程来执行
- DiscardPolicy, 忽略此任务
- DiscardOldestPolicy, 忽略最早的任务(最先加入队列的任务)
-
自定义拒绝策略
-
自定义拒绝策略只需要新建一个
RejectedExecutionHandler对象, 然后重写它的rejectedExecution()方法即可 -
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new RejectedExecutionHandler() { // 添加自定义拒绝策略 @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 业务处理方法 System.out.println("执行自定义拒绝策略"); } }); for (int i = 0; i < 6; i++) { executor.execute(() -> { System.out.println(Thread.currentThread().getName()); }); }
-
3. 线程池的工作队列有哪些?
ArrayBlockingQueue, 是一个用数组实现的有界阻塞队列, 按 FIFO 排序任务, 支持公平锁和非公平锁LinkedBlockingQueue, 基于链表结构的阻塞队列, 按 FIFO 排序任务, 容量可以选择进行设置, 不设置的话, 将是一个无边界的阻塞队列, 最大长度为Integer.MAX_VALUE, 吞吐量通常要高于ArrayBlockingQueneDelayQueue, 是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序, 否则根据插入到队列的先后排序PriorityBlockingQueue, 是具有优先级的无界阻塞队列, 不能保证同优先级元素的顺序SynchronousQueue, 一个不存储元素的阻塞队列, 每个插入操作必须等到另一个线程调用移除操作, 否则插入操作一直处于阻塞状态, 吞吐量通常要高于LinkedBlockingQueueLinkedBlockingDeque, 一个由链表结构组成的双向阻塞队列, 队列头尾都可以插入和移除元素, 多线程并发时, 可以将锁的竞争最多 降到一半
4. ThreadPoolExecutor 如何实现扩展?
- 通过重写
beforeExecute()和afterExecute()方法, 我们可以在扩展方法中添加日志或者实现数据统计, 比如统计线程的执行时间
关于 Executors 内的线程池对象
-
Executors源码中Executors.newFixedThreadPool()、Executors.newSingleThreadExecutor()和Executors.newCachedThreadPool()等方法的底层都是通过ThreadPoolExecutor实现的FixedThreadPool(固定数目线程的线程池)- 适用于处理 CPU 密集型的任务, 确保 CPU 在长期被工作线程使用的情况下, 尽可能的少的分配线程
- 特点
- 核心线程数和最大线程数大小一样
keepAliveTime为 0- 阻塞队列为
LinkedBlockingQueue
CachedThreadPool(可缓存线程的线程池)- 适用于并发执行大量短期的小任务
- 特点
- 核心线程数为 0
- 最大线程数为
Integer.MAX_VALUE - 阻塞队列为
SynchronousQueue - 非核心线程空闲存活时间为 60 秒
SingleThreadExecutor(单线程的线程池)- 适用于串行执行任务的场景, 一个任务一个任务地执行
- 特点
- 核心线程数为 1
- 最大线程数也为 1
- 阻塞队列是
LinkedBlockingQueue keepAliveTime为 0
ScheduledThreadPool(定时及周期执行的线程池)- 周期性执行任务的场景, 需要限制线程数量的场景
- 特点
- 最大线程数为
Integer.MAX_VALUE - 阻塞队列是
DelayedWorkQueue keepAliveTime为 0scheduleAtFixedRate()按某种速率周期执行scheduleWithFixedDelay()在某个延迟后执行
- 最大线程数为
-
在阿里巴巴的《 Java 开发手册 》中是这样规定的:
-
线程池不允许使用 Executors 去创建, 而是通过 ThreadPoolExecutor 的方式, 这样的处理方式让写的读者更加明确线程池的运行规则, 规避资源耗尽的风险。
-
Executors返回的线程池对象的弊端如下:FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE, 可能会堆积大量的请求, 从而导致 OOMCachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE, 可能会创建大量的线程, 从而导致 OOM
-