Java中的线程和线程池
什么是线程
线程是CPU进行程序调度的最小单元。线程之间拥有独立的栈空间和程序计数器。
两种使用方式
- 继承Thread类
- 实现Runnable接口
线程执行时会调用Runnable接口的run()方法。
线程状态
NEW 已创建,未启动
RUNNABLE 在JVM中,线程正在执行或等待CPU调度
BLOCKED 阻塞,等待监视器锁
WAITING 不定期等待其他线程执行特定操作,如Object.wait Thread.join LockSupport.park
TIMED_WAITING 带超时时间的等待其他线程指定特定操作,如 Thread.sleep、0bject.wait、 Thread.join、 LockSupport.parkNanos、 LockSupport.parkUntil
TERMINATED 终止,线程执行完成或发生异常后退出
线程实现
当调用Thread类的start()方法时,会转到Native层,使用C++的线程实现。
// C++中Thread类结构
// - Thread
// - NamedThread
// - VMThread
// - ConcurrentGCThread
// - WorkerThread
// - GangWorker
// - GCTaskThread
// - JavaThread
// - WatcherThread
其中JavaThread对应Java中的Thread。
//hotspot/src/share/vm/prims/jvm.cpp
//Java层Thread类的start()方法实现
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
//线程栈大小
jlong size =
java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
size_t sz = size > 0 ? (size_t) size : 0;
//创建Native层的线程JavaThread实例
//thread_entry是一个函数指针,当线程启动后会首先调用run函数,然后间接调用该函数,其内部会回调Java层的run()方法
native_thread = new JavaThread(&thread_entry, sz);
if (native_thread->osthread() != NULL) {
// Note: the current thread is not being used within "prepare".
native_thread->prepare(jthread);
}
//JavaThread创建完成后,子线程已经开始执行,但处于阻塞状态,所以还需要唤醒子线程
Thread::start(native_thread);
JVM_END
//线程执行入口函数,回调Java层的run()方法
static void thread_entry(JavaThread* thread, TRAPS) {
HandleMark hm(THREAD);
Handle obj(THREAD, thread->threadObj());
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,
obj,
KlassHandle(THREAD, SystemDictionary::Thread_klass()),
//这里的run_methond_name()方法返回的就是run,类型是Symbol,它定义在vmSymbols.hpp文件中
//template(run_method_name, "run") run方法的定义,返回Symbol类型
vmSymbols::run_method_name(),
//template(void_method_signature, "()V") run方法的签名
vmSymbols::void_method_signature(),
THREAD);
}
下面看一下本地线程的创建和启动过程:
//hotspot/src/share/vm/runtime/thread.cpp
//JavaThread构造函数
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
Thread() {
//初始化各种参数
initialize();
//存储线程执行入口函数
set_entry_point(entry_point);
//创建一个系统线程,这里要区分不同的系统平台
//这里有可能因为活跃线程过多,导致内存溢出,从而创建线程失败
os::ThreadType thr_type = os::java_thread;
thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
os::java_thread;
os::create_thread(this, thr_type, stack_sz);
}
创建本地线程JavaThread后,调用Thread类函数start来启动。
void Thread::start(Thread* thread) {
trace("start", thread);
// Start is different from resume in that its safety is guaranteed by context or
// being called from a Java method synchronized on the Thread object.
if (!DisableStartThread) {
if (thread->is_Java_thread()) {
// Initialize the thread state to RUNNABLE before starting this thread.
// Can not set it after the thread started because we do not know the
// exact thread state at that time. It could be in MONITOR_WAIT or
// in SLEEPING or some other state.
//更新线程状态为RUNNABLE
java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
java_lang_Thread::RUNNABLE);
}
//然后调用os类的start_thread函数
os::start_thread(thread);
}
}
//子线程初始化完成后,会回调到该函数
void JavaThread::run() {
//一些初始化动作
//thread-local alloc buffer 相关域
//记录实际的栈基址和大小
//初始化 thread local storage
...
thread_main_inner();
}
void JavaThread::thread_main_inner() {
//如果在创建过程中没有异常,并且在启动之前没有被停止,
//就会调用入口函数指针entry_point()指向的函数thread_entry,该函数在JavaThread类创建时指定
if (!this->has_pending_exception() &&
!java_lang_Thread::is_stillborn(this->threadObj())) {
{
ResourceMark rm(this);
this->set_native_thread_name(this->get_thread_name());
}
HandleMark hm(this);
this->entry_point()(this, this);
}
//子线程执行完成,执行exit函数,并删除JavaThread结构体
this->exit(false);
delete this;
}
下面看一下linux系统上创建线程的实现:
//hotspot/src/os/linux/vm/os_linux.cpp
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
//创建OSThread实例
OSThread* osthread = new OSThread(NULL, NULL);
if (osthread == NULL) {
return false;
}
//本地线程的状态,和Java层定义的状态有点不太一样
osthread->set_state(ALLOCATED);
//将OSThread设置给JavaThread,一一对应
thread->set_osthread(osthread);
//初始化线程属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//设置栈大小
//使用pthread_create创建线程,返回0代表失败,否则成功
//java_start是线程启动函数,其内部会调用JavaThread的run函数
//新创建的子线程会不停的检查OSThread状态,当主线程调用start_thread会将状态改为RUNNABLE,之后子线程才会继续执行。
pthread_t tid;
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
//存储线程id到OSThread中
osthread->set_pthread_id(tid);
//这里要阻塞等待新创建的子线程初始化完成或撤销
{
Monitor* sync_with_child = osthread->startThread_lock();
MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
while ((state = osthread->get_state()) == ALLOCATED) {
sync_with_child->wait(Mutex::_no_safepoint_check_flag);
}
}
return true;
}
//子线程入口函数
static void *java_start(Thread *thread) {
...
//和父线程通信,一直阻塞,直到主线程调用start_thread
{
MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
// 子线程初始化完成后要要通知主线程继续执行
osthread->set_state(INITIALIZED);
sync->notify_all();
// 然后等待start_thread函数的调用
while (osthread->get_state() == INITIALIZED) {
sync->wait(Mutex::_no_safepoint_check_flag);
}
}
//当调用了start()方法后,子线程继续执行,回调到JavaThread的run()函数
thread->run();
}
//变更了线程状态后,还要主动唤醒子线程
void os::pd_start_thread(Thread* thread) {
OSThread * osthread = thread->osthread();
Monitor* sync_with_child = osthread->startThread_lock();
MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
//唤醒新创建的子线程
sync_with_child->notify();
}
//hotspot/src/share/vm/runtime/os.cpp
void os::start_thread(Thread* thread) {
// guard suspend/resume
MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);
OSThread* osthread = thread->osthread();
//更新本地线程的状态为RUNNABLE,子线程检查到状态变更,才会继续执行JavaThread的run函数
osthread->set_state(RUNNABLE);
pd_start_thread(thread);
}
总结:
Java中的线程Thread本质就是系统线程的实现,其创建过程会区分不同的系统平台,如Linux(pthread_create)、windows(_beginthreadex)、posix、solaris(thr_create)、bsd、aix。
当我们调用Thread类的成员方法start()时,在Native层会创建一个对应平台的系统子线程,子线程创建并初始化完成后,会回调到Java层的run()方法,从而实现了在子线程中执行run()方法的功能。
线程操作
Thread currentThread()
静态Native方法,返回当前正在执行的线程对象的引用。
//hotspot/src/share/vm/prims/jvm.cpp
JVM_ENTRY(jobject, JVM_CurrentThread(JNIEnv* env, jclass threadClass))
JVMWrapper("JVM_CurrentThread");
//这里的thread为JavaThread实例,threadObj()函数返回一个oop类型的指针,然后再转换成Java引用类型。
//oop是普通对象指针,用来在JVM中表示一个Java对象。
oop jthread = thread->threadObj();
assert (thread != NULL, "no current thread!");
return JNIHandles::make_local(env, jthread);
JVM_END
//hotspot/src/share/vm/runtime/jniHandles.cpp
//将oop类型转换为jobject类型
jobject JNIHandles::make_local(JNIEnv* env, oop obj) {
if (obj == NULL) {
return NULL; // ignore null handles
} else {
JavaThread* thread = JavaThread::thread_from_jni_environment(env);
assert(Universe::heap()->is_in_reserved(obj), "sanity check");
return thread->active_handles()->allocate_handle(obj);
}
}
void yield()
静态Native方法,线程让步,暗示调度器当前线程可以让出CPU的使用权,让其他具有同样优先级的线程可得以执行,当前线程的状态从运行状态变成可运行状态,但调度器可以忽略此暗示,也就是不一定会生效。
//hotspot/src/share/vm/prims/jvm.cpp
JVM_ENTRY(void, JVM_Yield(JNIEnv *env, jclass threadClass))
JVMWrapper("JVM_Yield");
//判断是否支持yield操作,如果不支持直接返回。JDK8版本中只有solaris平台返回true。
if (os::dont_yield()) return;
#ifndef USDT2
HS_DTRACE_PROBE0(hotspot, thread__yield);
#else /* USDT2 */
HOTSPOT_THREAD_YIELD();
#endif /* USDT2 */
// When ConvertYieldToSleep is off (default), this matches the classic VM use of yield.
// 用sleep操作模拟yield,目前JDK版本为false
if (ConvertYieldToSleep) {
os::sleep(thread, MinSleepInterval, false);
} else {
os::yield();
}
JVM_END
对于yield()操作,不同平台提供了不同的系统接口,例如Linux上是sched_yield()函数,Windows上是NakedYield()函数,而Solaris平台使用sleep接口替代。
void os::yield() {
//sched_yield()这个函数可以使用另一个级别等于或高于当前线程的线程先运行。
//如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序。
sched_yield();
}
void sleep(long millis) / void sleep(long millis, int nanos)
静态Native方法,使当前线程停止执行,睡眠指定时间,线程状态从可运行变为限时等待,睡眠期间会让出CPU资源,但不会释放任何监视器锁。
//hotspot/src/is/linux/vm/os_linux.cpp
//Linux平台实现的线程睡眠
//millis 代表睡眠时间
//interruptible 代表是否响应中断
int os::sleep(Thread* thread, jlong millis, bool interruptible) {
assert(thread == Thread::current(), "thread consistency check");
ParkEvent * const slp = thread->_SleepEvent ;
slp->reset() ;
OrderAccess::fence() ;
if (interruptible) {
jlong prevtime = javaTimeNanos();
for (;;) {
//当线程被唤醒后,查询是否设置了中断状态,如果是则返回OS_INTRPT,调用者则会抛出InterruptedException
if (os::is_interrupted(thread, true)) {
return OS_INTRPT;
}
jlong newtime = javaTimeNanos();
if (newtime - prevtime < 0) {
// time moving backwards, should only happen if no monotonic clock
// not a guarantee() because JVM should not abort on kernel/glibc bugs
assert(!Linux::supports_monotonic_clock(), "time moving backwards");
} else {
millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;
}
if(millis <= 0) {
return OS_OK;
}
prevtime = newtime;
{
assert(thread->is_Java_thread(), "sanity check");
JavaThread *jt = (JavaThread *) thread;
ThreadBlockInVM tbivm(jt);
OSThreadWaitState osts(jt->osthread(), false /* not Object.wait() */);
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition() or
// java_suspend_self() via check_and_wait_while_suspended()
//调用ParkEvent的park函数,使当前休眠指定时间,直到被unpark函数唤醒
slp->park(millis);
// were we externally suspended while we were waiting?
jt->check_and_wait_while_suspended();
}
}
} else {
OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
jlong prevtime = javaTimeNanos();
for (;;) {
// It'd be nice to avoid the back-to-back javaTimeNanos() calls on
// the 1st iteration ...
jlong newtime = javaTimeNanos();
if (newtime - prevtime < 0) {
// time moving backwards, should only happen if no monotonic clock
// not a guarantee() because JVM should not abort on kernel/glibc bugs
assert(!Linux::supports_monotonic_clock(), "time moving backwards");
} else {
millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;
}
//如果不响应中断,则结束循环,返回OS_OK
if(millis <= 0) break ;
prevtime = newtime;
slp->park(millis);
}
return OS_OK ;
}
}
stop() destroy() suspend() resume() countStackFrames()
这几个线程实例方法被标识弃用。
stop()方法弃用的原因是stop()方法会引起监视器锁的释放,从而导致被锁保护的对象处于不确定的状态。
destroy()方法是没有实现的方法。当初设计该方法是为了在不处理任何资源的情况销毁当前线程,但这样容易产生死锁。当线程持有锁的情况被调用destroy()方法,锁将永远无法释放。
suspend()和resume()方法被弃用的原因也是因为容易产生死锁,因为suspend()的调用并不会释放监视器锁,如果一个线程想要在加锁的条件调用已经处于suspend状态的线程的resume()方法,那么就会产生死锁,这种死锁情况叫做冰冻过程。
static boolean interrupted() / void interrupt() / boolean isInterrupted()
static boolean interrupt() 测试当前所处线程是否被中断,true代表已被中断,false代表没有被中断。该方法的调用会清除中断状态,所以第一次调用时返回true,连续第二次调用则返回false。
void interrupt() 中断某个thread实例对象代表的线程。中断并不会真的停止线程,而是设置线程的中断标志位,这里会根据线程不同的状态来产生不同的结果:
- 如果线程被阻塞着(wait、sleep、join),线程会收到InterruptException异常,并清除掉中断状态。
- 如果线程被IO操作阻塞着(InterruptibleChannel),然后线程会收到ClosedByInterruptException异常,channel被关闭,中断状态被设置。
- 如果线程被Selector阻塞着,然后线程中断状态被设置,selection操作会立即返回一个非0的结果,Selector的wakeup()方法会被调用。
boolean isInterrupted() 测试某个thread实例对象代表的线程是否被中断,该方法不会清楚中断状态。
当线程未处于活动状态时会忽略线程中断请求,所以此时检查中断状态会返回false。
设置线程中断:
//hotspot/src/is/linux/vm/os_linux.cpp
//JavaThread中存在三个成员变量
ParkEvent * _ParkEvent ; // 用于synchronized()同步代码块和Object.wait()引起的线程阻塞
ParkEvent * _SleepEvent ;// 用于Thread.sleep引起的阻塞
Parker* _parker; // 为Unsafe类的park/unpark提供支持,用于JUC包中各种锁引起的阻塞
void os::interrupt(Thread* thread) {
assert(Thread::current() == thread || Threads_lock->owned_by_self(),
"possibility of dangling Thread pointer");
//取出JavaThread对应的系统线程OSThread
OSThread* osthread = thread->osthread();
if (!osthread->interrupted()) {
//设置终端标识
osthread->set_interrupted(true);
// More than one thread can get here with the same value of osthread,
// resulting in multiple notifications. We do, however, want the store
// to interrupted() to be visible to other threads before we execute unpark().
//内存屏障,使中断状态能够被其他线程可见
OrderAccess::fence();
//唤醒由Thread.sleep引起的阻塞
ParkEvent * const slp = thread->_SleepEvent ;
if (slp != NULL) slp->unpark() ;
}
// For JSR166. Unpark even if interrupt status already was set
//JSR166主要是对JUC的技术规范,这里就是唤醒由JUC包的锁引起的阻塞
if (thread->is_Java_thread())
((JavaThread*)thread)->parker()->unpark();
//唤醒由Synchronized()和Object.wait()引起的阻塞
ParkEvent * ev = thread->_ParkEvent ;
if (ev != NULL) ev->unpark() ;
}
//hotspot/src/share/vm/runtime/osThread.hpp
class OSThread: public CHeapObj<mtThread> {
volatile jint _interrupted; // 线程中断状态
//线程中断就是设置成员变量_interrupted的值
volatile bool interrupted() const { return _interrupted != 0; }
void set_interrupted(bool z) { _interrupted = z ? 1 : 0; }
}
查询线程中断状态:
//hotspot/src/is/linux/vm/os_linux.cpp
bool os::is_interrupted(Thread* thread, bool clear_interrupted) {
OSThread* osthread = thread->osthread();
bool interrupted = osthread->interrupted();
//如果需要清除中断状态,再次设置为false
if (interrupted && clear_interrupted) {
osthread->set_interrupted(false);
}
return interrupted;
}
在前面sleep的部分,我们看到了InterruptedException是如何被抛出的。那对于NIO的通道操作,是如何响应中断的呢?
答案在Thread.java类中的interrupt()方法中:
//中断当前线程
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
//这里有个Interruptible类型的成员变量blocker,它是一个接口类,里面包含一个interrupt(Thread t)
//线程被唤醒后,如果设置了Interruptible,则回调其interrupt方法。
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
//设置中断回调
void blockedOn(Interruptible b) {
synchronized (blockerLock) {
blocker = b;
}
}
那么什么时候调用的blockedOn(Interruptible b)方法呢?
// java/nio/channels/spi/AbstractInterruptibleChannel.java
//当前线程准备操作IO时,会先调用begin()方法,添加线程中间回调
protected final void begin() {
if (interruptor == null) {
interruptor = new Interruptible() {
public void interrupt(Thread target) {
synchronized (closeLock) {
if (!open)
return;
open = false;
interrupted = target;
try {
AbstractInterruptibleChannel.this.implCloseChannel();
} catch (IOException x) { }
}
}};
}
blockedOn(interruptor);
Thread me = Thread.currentThread();
if (me.isInterrupted())
interruptor.interrupt(me);
}
static void blockedOn(Interruptible intr) {
//这里最终会调用到Thread类的blockedOn(Interruptible b)方法
//因为blockOn方法是包级私有的,所以需要借助JavaLangAccess接口间接访问
sun.misc.SharedSecrets.getJavaLangAccess()
.blockedOn(Thread.currentThread(),intr);
}
final native boolean isAlive()
判断当前线程是否活着,线程活着就是已经被启动,而且没有死亡。
//hotspot/src/share/vm/classfile/javaClasses.cpp
//判断一个线程是否活着,就是检查Native层的JavaThread指针是否不为空
bool java_lang_Thread::is_alive(oop java_thread) {
JavaThread* thr = java_lang_Thread::thread(java_thread);
return (thr != NULL);
}
final synchronized void join(long millis)
等待当前线程死亡。
join方法内部就是通过Object的wait(int timeout)方法阻塞当前运行线程的。
public final void join(long millis)
throws InterruptedException {
synchronized(lock) {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
lock.wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
lock.wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
}
既然有wait,那么当线程结束时肯定会调用notify或notifyAll的。
前面查看interrupt()方法时发现,当线程被Object.wait()方法阻塞时,会通过ParkEvent的unpark()函数取消线程阻塞。
那么线程结束时,是不是也是同样的操作呢?
前面在说明线程启动的流程中,在thread_main_inner函数中会回调Java的run()方法,执行结束后会调用exit(boolean destroy_vm, ExitType exit_type);来结束Native线程。
//hotspot/src/share/vm/runtime/thread.cpp
//Java层线程run()方法执行结束后,结束Native层线程
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
...
if (!destroy_vm || JDK_Version::is_jdk12x_version()) {
...
//如果不是编译线程,回调Thread类的exit()方法
if (!is_Compiler_thread()) {
int count = 3;
while (java_lang_Thread::threadGroup(threadObj()) != NULL && (count-- > 0)) {
EXCEPTION_MARK;
JavaValue result(T_VOID);
KlassHandle thread_klass(THREAD, SystemDictionary::Thread_klass());
JavaCalls::call_virtual(&result,
threadObj, thread_klass,
vmSymbols::exit_method_name(),
vmSymbols::void_method_signature(),
THREAD);
CLEAR_PENDING_EXCEPTION;
}
}
} else {}
//唤醒等待当前线程的等待者
ensure_join(this);
}
static void ensure_join(JavaThread* thread) {
Handle threadObj(thread, thread->threadObj());
ObjectLocker lock(threadObj, thread);
//清除挂起的异常,因为线程要退出了
thread->clear_pending_exception();
//设置线程状状态为TERMINATED已终止
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
//设置本地JavaThread指针为NULL,之后再调用isAlive()方法就返回false了
java_lang_Thread::set_thread(threadObj(), NULL);
//唤醒等待此线程结束的线程,也就是执行join()方法的线程
lock.notify_all(thread);
//又清除一次异常
thread->clear_pending_exception();
}
static native boolean holdsLock(Object obj)
检查仅当前线程是否持有指定对象的监视器锁。该方法被设计用来允许程序来断言当前线程持有指定的对象锁。
//hotspot/src/share/vm/runtime/synchronizer.cpp
//thread 代表当前线程
//h_obj 代表锁对象
bool ObjectSynchronizer::current_thread_holds_lock(JavaThread* thread,
Handle h_obj) {
// 使用偏向锁
if (UseBiasedLocking) {
BiasedLocking::revoke_and_rebias(h_obj, false, thread);
assert(!h_obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
assert(thread == JavaThread::current(), "Can only be called on current thread");
oop obj = h_obj();
//从对象中取出标志位,标志位中记录了锁状态
markOop mark = ReadStableMark (obj) ;
// Uncontended case, header points to stack
//在非竞争条件下,一般是存在轻量级锁(锁标识为0),这时标志位中存储的是栈帧地址,然后判断栈帧地址是否包含在线程中
if (mark->has_locker()) {
return thread->is_lock_owned((address)mark->locker());
}
// Contended case, header points to ObjectMonitor (tagged pointer)
//在竞争条件下,如果锁标识为2代表持有监视器锁。这时标志位中存储的是指向监视器对象的指针。
//然后再判断该指针是否在栈地址空间范围内
if (mark->has_monitor()) {
ObjectMonitor* monitor = mark->monitor();
return monitor->is_entered(thread) != 0 ;
}
// Unlocked case, header in place
assert(mark->is_neutral(), "sanity check");
return false;
}
StackTraceElement[] getStackTrace() 和 static Map<Thread, StackTraceElement[]> getAllStackTraces()
getStackTrace()是获取当前线程的栈调用信息,如果线程已死则返回空数组。
getAllStackTraces()是获取所有存活线程的栈调用信息。
void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) 和 static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)
设置未捕获异常处理器。
除了手动设置外,ThreadGroup类也实现了UncaughtExceptionHandler接口。他们三个的优先级如下:
uncaughtExceptionHandler > group > defaultUncaughtExceptionHandler
当异常发生时,JVM会回调dispatchUncaughtException(Throwable e)方法,将异常传递到Java层。
之后按照上述的优先级,逐个调用UncaughtExceptionHandler接口的uncaughtException(Thread, Throwable)方法
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
下面看下dispatchUncaughtException方法是如何被调用的。
再次到JavaThread线程退出函数:
//hotspot/src/share/vm/runtime/thread.cpp
//Java层线程run()方法执行结束后,结束Native层线程
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
...
//取出等待处理的异常
Handle uncaught_exception(this, this->pending_exception());
this->clear_pending_exception();
//如果异常不为空,则将异常分发给Java层
//旧版本的JDK是调用ThreadGroup的uncaughtException()方法
//新版本的JDK是调用Thread的dispatchUncaughtException()方法
if (uncaught_exception.not_null()) {
Handle group(this, java_lang_Thread::threadGroup(threadObj()));
{
EXCEPTION_MARK;
// Check if the method Thread.dispatchUncaughtException() exists. If so
// call it. Otherwise we have an older library without the JSR-166 changes,
// so call ThreadGroup.uncaughtException()
KlassHandle recvrKlass(THREAD, threadObj->klass());
CallInfo callinfo;
KlassHandle thread_klass(THREAD, SystemDictionary::Thread_klass());
LinkResolver::resolve_virtual_call(callinfo, threadObj, recvrKlass, thread_klass,
vmSymbols::dispatchUncaughtException_name(),
vmSymbols::throwable_void_signature(),
KlassHandle(), false, false, THREAD);
CLEAR_PENDING_EXCEPTION;
methodHandle method = callinfo.selected_method();
//这里先判断了dispatchUncaughtException()方法是否存在
if (method.not_null()) {
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,
threadObj, thread_klass,
vmSymbols::dispatchUncaughtException_name(),
vmSymbols::throwable_void_signature(),
uncaught_exception,
THREAD);
} else {
KlassHandle thread_group(THREAD, SystemDictionary::ThreadGroup_klass());
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,
group, thread_group,
vmSymbols::uncaughtException_name(),
vmSymbols::thread_throwable_void_signature(),
threadObj, // Arg 1
uncaught_exception, // Arg 2
THREAD);
}
if (HAS_PENDING_EXCEPTION) {
ResourceMark rm(this);
jio_fprintf(defaultStream::error_stream(),
"\nException: %s thrown from the UncaughtExceptionHandler"
" in thread \"%s\"\n",
pending_exception()->klass()->external_name(),
get_thread_name());
CLEAR_PENDING_EXCEPTION;
}
}
}
...
}
ThreadShadow类中包含了这个异常的成员变量,ThreadShadow是所有线程类的基类。
//hotspot/src/share/vm/utilities/exceptions.hpp
class ThreadShadow: public CHeapObj<mtThread> {
oop _pending_exception;
oop pending_exception() const { return _pending_exception; }
bool has_pending_exception() const { return _pending_exception != NULL; }
void set_pending_exception(oop exception, const char* file, int line);
}
那么是什么给线程设置的异常呢?
调用地方有好几个,区分当前使用的是字节码解释器,还是运行时。这里以字节码解释器为例:
//hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp
//当遇到throw字节码时,为线程赋值需要抛出的异常。然后跳转到handle_exception处继续执行。
CASE(_athrow): {
oop except_oop = STACK_OBJECT(-1);
CHECK_NULL(except_oop);
// set pending_exception so we use common code
THREAD->set_pending_exception(except_oop, NULL, 0);
goto handle_exception;
}
void setName(String) / String getName()
设置/获取线程名称
private volatile char name[];
//设置线程名称
public final synchronized void setName(String name) {
checkAccess();
this.name = name.toCharArray();
if (threadStatus != 0) {
setNativeName(name);
}
}
//获取线程名称
public final String getName() {
return new String(name, true);
}
可以看出线程名称就是存储在Thread的对象的成员变量中,虽然调用了setNativeName(name)函数,将线程名称传递给了Native层,但是在JDK8版本上,大部分系统平台的本地实现是空的。
//hotspot/src/is/linux/vm/os_linux.cpp
void os::set_native_thread_name(const char *name) {
// Not yet implemented.
return;
}
bsd平台有对应实现。
//hotspot/src/is/linux/vm/os_bsd.cpp
void os::set_native_thread_name(const char *name) {
#if defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5
// This is only supported in Snow Leopard and beyond
if (name != NULL) {
// Add a "Java: " prefix to the name
char buf[MAXTHREADNAMESIZE];
snprintf(buf, sizeof(buf), "Java: %s", name);
pthread_setname_np(buf);
}
#endif
}
到了JDK9后,才添加上了对应实现:会把名称的前15个字符通过pthread_setname_np传递给操作系统。
线程池 ThreadPoolExecutor
线程池解决了两件事:
- 当需要执行大量任务时,优化了性能。
- 包含一些基础的统计功能,如任务执行完成的数量。
Executors提供了几个工厂方法,方便开发者更容易使用线程池。
- newCachedThreadPool 无边界的线程池,自动创建需要的线程
- newFixedThreadPool 固定数量的线程池
- newSingleThreadExecutor 单线程
核心线程数coreSize和最大线程数maximumSize
- 当前线程数少于coreSize时,总会新创建线程来执行任务,尽管有可能其他线程处于空闲状态。
- 当前线程数大于coreSize时,只有当任务等待队列满了之后,才会新建线程。
- 当coreSize与maximumSize相等时,就是固定数量类型的线程池。
- 除了在构造函数中指定这两个值,也可以动态修改。
一般情况下,只有当提交新任务时才会创建线程,但是也可以通过复写prestartCoreThread()和prestartAllCoreThreads()方法来告诉线程池在创建时就立马创建新线程,来执行任务队列中的任务。
新线程的创建是通过ThreadFactory完成的,线程池提供了默认的线程池DefaultThreadFactory,由它创建出来的线程:
- 和父线程是同一个线程组
- 线程名称格式为 pool-[线程池号]-thread-[线程号]
- 非守护线程
- 优先级为NORM_PRIORITY
我们可以自定义ThreadFactory来修改上述配置。
线程空闲存活时间keep-alive times
如果当前线程数大于coreSize,超出的线程数将会在空闲时间大于存活时间后被终止掉,来减少资源消耗。当有需要后,再重新创建新的线程。如果设置的存活时间为Long.MAX_VALUE, TimeUnit.NANOSECONDS,则在线程池被关闭前,空闲线程永远不会被终止。一般情况下,keep-alive times只对超出核心线程数的那些线程有效。但我们可以通过allowCoreThreadTimeOut(boolean)方法让keep-alive times对核心线程也生效。
任何一个阻塞队列BlockingQueue都可以用来传输和保存被提交的任务。
- 如果当前线程数小于核心线程数,线程池总会创建新线程来执行任务,而不是加入任务队列。
- 如果当前线程数大于等于核心线程数时,线程池总是优先将请求加入队列,而不是创建新线程
- 如果队列已满,才会创建新线程。但如果超出了最大线程数,这个任务会被拒绝。
队列类型
- 直接转移 一个比较好的选择就是使用同步队列SynchronousQueue,一旦存入一个任务就必须等待对应取出动作,然后交给一个线程去执行,从而避免额外持有这些任务。这个策略还可以避免由于多个任务之间存在内部依赖而导致的锁住问题。直接转移的策略一般需要无限的核心线程数,从而避免提交任务被拒绝。反过来,如果任务提交速度超过了执行速度,可能会导致线程数的持续增长。
- 无限队列 不指定容量的LinkedBlockingQueue,超出核心线程数会将任务存入队列中。适应于瞬间大量请求,但请求之间没有依赖关系的场景。
- 有限队列 ArrayBlockingQueue和有限的最大线程数配合,可以帮助避免资源被耗尽,但是会比较难协调和控制。队列大小和最大线程数需要权衡:大队列+小线程池可以降低CPU、系统资源的占用和上下文切换的消耗,但是会降低吞吐量;小队列+大线程池会保持CPU忙碌,但是会导致频繁的线程调度,也会降低吞吐量。
综上所述,coreSize、maximumSize、任务队列这三个参数要根据业务场景(CPU密集型/IO密集型)和CPU核心数来综合考虑选择。
任务拒绝
当线程池无法接收任务请求时(线程池关闭、队列满、超出最大线程数),会调用任务拒绝执行处理器RejectExecutionHandler的回调rejectExecution(Runnable, ThreadPoolExecutor),可以在构造函数中指定,ThreadPoolExecutor提供四种实现:
- AbortPolicy 默认策略,即抛出RejectedExecutionException异常。
- CallerRunsPolicy 在创建线程池的线程再次执行被拒绝的任务
- DiscardPolicy 安静的丢弃这个任务
- DiscardOldestPolicy 丢弃最早的任务,然后重试当前被拒绝的任务
Hook方法
ThreadPoolExecutor提供俩个方法:beforeExecute(Thread, Runnable)和afterExecute(Runnable, Throwable),它们分别在每个任务开始和结束时调用,开发者可通过这两个方法来控制执行环境:如重新初始化ThreadLocals、收集统计数据、添加日志输出等。
另外还可以通过复写terminated方法,在线程池被关闭时执行特殊的处理。
如果在复写方法中产生了,内部的工作线程会被立即终止。
终止
当线程池不再被引用并且没有活跃线程时,线程池会自动关闭。如果你想重用没有引用的线程池,要确保线程池里面未使用的线程会最终死亡,例如通过设置合适的keep-alive时间、使用0核心线程数,降低的最大线程数、或者设置核心线程超时时间。
ThreadPoolExecutor的类关系为:
ThreadPoolExecutor extends AbstractExecutorService implements ExecutorService extends Executor
Executor(执行器)提供了执行一个Runnable任务的能力,其实现可以是在线程池中新线程中执行,也可以是在调用线程中执行。
ExecutorService(执行服务)提供了任务执行管理的能力,通过它可以提交单个任务、执行多个多个任务、停止任务执行等。并提供了获取任务执行结果的机制(返回Future),调用者可以方便的获知任务执行结束和返回值。Future是并发包JUC下的一个接口,稍后我们讨论其实现目的和方式。
AbstractExecutorService是ExecutorService的默认实现,不过返回值改成了RunnableFuture,run方法执行完成后会调用Future的完成方法,并允许访问执行结果。开发者在自定义线程池时,也可以通过复写newTaskFor(…)方法来返回自己实现的RunnableFuture子类。
最大有效线程数和线程池状态
ThreadPoolExecutor用一个AtomicInteger原子整型类来记录workerCount和runState,整型一共有32位,其中028位用来标识任务数,2931位用来标识线程池状态。所以最大任务数为(2^29)-1。各个线程池状态含义和对应的值为:
- RUNNING 正在运行,可以接受新任务并处理队列中的任务 -536870912
- SHUTDOWN 关闭,不接受新任务,但可以处理队列中的任务 0
- STOP 停止,不接受新任务,不处理队列中的任务,并且中断正在执行的任务 536870912
- TIDYING 整理完毕,所有任务已终止,工作线程数为0,执行terminated()方法 1073741824
- TERMINATED 已终止,terminated已经被调用 1610612736
任务处理流程
下面我们分析源码,看看当我们调用线程池的execute(Runnable)方法时,任务是如何被执行的?
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
1、判断当前任务数是否小于核心线程数。如果成立,则将创建一个worker(Runnable+Thread),并执行
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2、如果超出了核心线程数,则优先将任务添加到队列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//检查线程池状态
if (! isRunning(recheck) && remove(command))
reject(command);
//再次检查Worker数量,避免此时产生了空闲线程
else if (workerCountOf(recheck) == 0)
//如果Worker数为0,则再次新建一个Worker,并执行workQueue队列中的任务
addWorker(null, false);
}
//3、如果加入队里失败,说明队列已满,则同第一步:创建一个worker(Runnable+Thread),并执行
else if (!addWorker(command, false))
//4、如果队列满,且创建Worker失败,则说明线程池饱和了,则执行任务拒绝的逻辑
reject(command);
}
上述过程涉及到了两个集合,一个是BlockingQueue workQueue队列,用来存放暂时处理不了的任务并把任务交接给Worker线程;另一个是HashSet workers不重复集合,它就是线程池中池概念的体验,上面代码addWorker(Runnable, boolean)就是创建一个Worker并添加到了workers中。每一个Work与一个Thread相关联,当线程池需要退出时,就遍历workers中的线程对象,并调用其中断方法interrupt()。
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
Worker创建和执行流程
//Worker创建和执行流程
//创建一个Worker
//firstTask是Worker执行的第一个任务
//this.threads是通过ThreadFactory创建的新线程,因为Worker也继承自Runnable接口,
//所以新线程会回调Worker的run()方法,从而调用到runWorker(Worker)方法
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
//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是构造Worker传入的任务,作为新线程执行的第一个任务
//第一任务执行完成后,通过getTask()从任务队列workQueue中继续取出等待任务来执行
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
//响应线程中断
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//任务执行前的hook方法
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()方法中:
从workQueue获取任务时,可以指定超时时间,这个超时时间就是核心线程,和非核心线程的空闲等待时间。
如果设置了该时间,并且在超时时间内没有新的任务可以执行,则线程退出。
否则将一直等待新的任务。
private Runnable getTask() {
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.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//BlockingQueue的poll方法可以设置超时时间。
//take()方法则是一直等待。其内部是使用ReentrantLock和Condition实现的线程锁定和唤醒
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
Future和Callable
Future代表了一个异步计算的返回值,它提供了几个方法用来检测异步计算是否完成,从而一直等待完成并获取到计算结果。获取计算结果只能使用get()方法,这个方法会阻塞一直到计算完成。
public interface Future<V> {
//等待计算结果,可以选择一直等待或超时等待
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
//取消计算任务的执行,返回true取消成功,false取消失败
boolean cancel(boolean mayInterruptIfRunning);
//判断任务是否被取消
boolean isCancelled();
//判断任务是否正常完成
boolean isDone();
}
那么get()方法是如何阻塞线程并获知计算结果的呢?
通过查看FutureTask的源码可以知道,get()方法的调用会使用LockSupport.park(Object)或LockSupport.parkNanos(Object, long)方法让当前线程休眠,而计算任务完成后则通过LockSupport(Object)唤醒前面等待的线程。
Callable和Runnable接口很像,他们都是被设计用来在子线程执行特定任务,然而不同的是Callable是有返回值的。我们使用Callable来执行子任务时就可以直接将返回值抛出来了。
为什么使用Future
我们都知道Java中的线程使用时是无法在主线程中直接获取执行结果,常见方式是通过消息队列的方式,将计算结果传递给主线程,例如Android系统中的跨线程通信机制Handler。但是单独维护这样的机制有些重,所以Future+Callable的出现就很好的解决了这个问题。它通过多线程共享堆内存的机制,子线程完成任务后将结存存储到一块堆内存中,然后通知主线程去取。
如何配置线程池
之前看过一遍美团技术团队发出的一篇关于Java线程池的文章,里面讲述了几个因为线程池配置不合理而产生的事故。
- 因为最大线程数较小,请求并发较多时,导致频繁产生RejectedExecutionException.
- 任务队列过长,请求并发较多时,大量任务堆积在队列中得不到处理,导致下游请求超时。
所以合理高效的配置线程池的各个参数是非常重要的。而线程池参数中最重要的应该就是最大线程数大小了。
通过一些文献可以得知,区分两种任务类型(IO密集型和CPU密集型)分别给出的线程数的建议是:
- IO密集型(文件读写、数据库交互、网络数据传输):2倍的CPU核心数 + 1
- CPU密集型(复杂算法):CPU核心数+1
例如在Android平台中的AsyncTask实现中,采用的最大线程数为MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1,任务队列长度为128,线程空闲存活时间为30s。
当然,这些建议都是在只有一个应用、一个线程池的前提下,而现实情况中,一台设备会运行多个程序,同一个程序的不同模块可能会使用自己的线程池,所以上述建议数量就会有些偏差。
为了尽量减少偏差,美团技术团队采用了线程池动态参数的方案,通过监控线程池执行任务的状态(任务等待时间、活跃线程数、任务拒绝执行异常等)来动态下发线程池控制的参数。
协程
协程的本质是在一个用户线程中,模拟操作系统,对多个协程进行调度,从而避免线程创建和切换的开销。
也就是说多个协程是运行在同一个线程中的,他们共享线程的内存空间。
协程出现的目的就是用写同步代码的方式,实现异步调用。
Isolate
Dart中的Isolate本质上是自己管理内存的线程,多个Isolate之间内存隔离,但是共享Dart虚拟机的内存。