作者:小牛呼噜噜 | https://xiaoniuhululu.com

计算机内功、JAVA底层、面试、职业成长相关资料等更多精彩文章在公众号「小牛呼噜噜

大家好,我是呼噜噜,最近一直在梳理Java并发,但内容杂且偏晦涩,今天我们一起来聊聊Java 线程的状态及转换 先来夯实一下基础,万丈高楼平地起,路还是得慢慢走。

Java线程的生命周期

我们先来看下Java线程的生命周期图:

上图也是本文的大纲,我们下面依次聊聊java各个线程状态及其他们的转换。

线程初始状态

线程初始状态(NEW): 当前线程处于线程被创建出来但没有被调用start()

在Java线程的时间中,关于线程的一切的起点是从Thread 类的对象的创建开始,一般实现Runnable接口 或者 继承Thread类的类,实例化一个对象出来,线程就进入了初始状态

  1. Thread thread = new Thread()

由于线程在我们操作系统中也是非常宝贵的资源,在实际开发中,我们常常用线程池来重复利用现有的线程来执行任务,避免多次创建和销毁线程,从而降低创建和销毁线程过程中的代价。Java 给我们提供了 Executor 接口来使用线程池,查看其JDK1.8源码,发现其内部封装了Thread t = new Thread()

  1. public class Executors {
  2. ...
  3. static class DefaultThreadFactory implements ThreadFactory {
  4. private static final AtomicInteger poolNumber = new AtomicInteger(1);
  5. private final ThreadGroup group;
  6. private final AtomicInteger threadNumber = new AtomicInteger(1);
  7. private final String namePrefix;
  8. ...
  9. public Thread newThread(Runnable r) {
  10. Thread t = new Thread(group, r,
  11. namePrefix + threadNumber.getAndIncrement(),
  12. 0);
  13. if (t.isDaemon())
  14. t.setDaemon(false);
  15. if (t.getPriority() != Thread.NORM_PRIORITY)
  16. t.setPriority(Thread.NORM_PRIORITY);
  17. return t;
  18. }
  19. }
  20. ...
  21. }

在thread类源码中,我们还能发现线程状态的枚举类State

  1. public enum State {
  2. /**
  3. * Thread state for a thread which has not yet started.
  4. */
  5. NEW,
  6. RUNNABLE,
  7. BLOCKED,
  8. WAITING,
  9. TIMED_WAITING,
  10. /**
  11. * Thread state for a terminated thread.
  12. * The thread has completed execution.
  13. */
  14. TERMINATED;
  15. }

所谓线程的状态,在java源码中都是通过threadStatus的值来表示的

  1. /* Java thread status for tools,
  2. * initialized to indicate thread 'not yet started'
  3. */
  4. private volatile int threadStatus = 0;

StatethreadStatus 通过toThreadState方法映射转换

  1. public State getState() {
  2. // get current thread state
  3. return sun.misc.VM.toThreadState(threadStatus);
  4. }
  5. //--- --- ---
  6. public static State toThreadState(int var0) {
  7. if ((var0 & 4) != 0) {
  8. return State.RUNNABLE;
  9. } else if ((var0 & 1024) != 0) {
  10. return State.BLOCKED;
  11. } else if ((var0 & 16) != 0) {
  12. return State.WAITING;
  13. } else if ((var0 & 32) != 0) {
  14. return State.TIMED_WAITING;
  15. } else if ((var0 & 2) != 0) {
  16. return State.TERMINATED;
  17. } else {
  18. return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
  19. }
  20. }

到这里我们就可以发现,Thread t = new Thread()在Java中只是设置了线程的状态,操作系统中并没有的实际线程的创建

线程运行状态

线程运行状态(RUNNABLE),线程被调用了start()等待运行的状态

在Linux操作系统层面,包含RunningReady 状态。其中Ready状态是等待 CPU 时间片。现今主流的JVM,比如hotspot虚拟机都是把Java 线程,映射到操作系统OS底层的线程上,把调度委托给了操作系统。而操作系统比如Linux,它是多任务操作系统,充分利用CPU的高性能,将CPU的时间分片,让单个CPU实现"同时执行"多任务的效果。

更多精彩文章在公众号「小牛呼噜噜

Linux的任务调度又采用抢占式轮转调度,我们不考虑特权进程的话OS会选择在CPU上占用的时间最少进程,优先在cpu上分配资源,其对应的线程去执行任务,尽可能地维护任务调度公平。RunningReady 状态的线程在CPU中切换状态非常短暂。大概只有 0.01 秒这一量级,区分开来意义不大,java将这2个状态统一用RUNNABLE来表示

thread.start()源码解析

我们接下来看看为什么说执行thread.start()后,线程的才"真正的创建"

  1. public class ThreadTest {
  2. /**
  3. * 继承Thread类
  4. */
  5. public static class MyThread extends Thread {
  6. @Override
  7. public void run() {
  8. System.out.println("This is child thread");
  9. }
  10. }
  11. public static void main(String[] args) {
  12. MyThread thread = new MyThread();
  13. thread.start();
  14. }
  15. }

其中thread.start()方法的源码中,会去调用start0()方法,而start0()private native void start0();JVM调用Native方法的话,会进入到不受JVM控制的世界里

Thread类实例化的同时,会首先调用registerNatives方法,注册本地Native方法,动态绑定JVM方法

  1. private static native void registerNatives();
  2. static {
  3. registerNatives();
  4. }

Thread类中通过registerNatives将指定的本地方法绑定到指定函数,比如start0本地方法绑定到JVM_StartThread函数:

  1. ...
  2. static JNINativeMethod methods[] = {
  3. {"start0", "()V", (void *)&JVM_StartThread},
  4. {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
  5. {"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
  6. ...

源码见:http://hg.openjdk.java.net/jdk8u/jdk8u60/jdk/file/935758609767/src/share/native/java/lang/Thread.c

JVM_StartThread 是JVM层函数,抛去各种情况的处理,主要是通过 new JavaThread(&thread_entry, sz)来创建JVM线程对象

  1. JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  2. JVMWrapper("JVM_StartThread");
  3. JavaThread *native_thread = NULL;
  4. //表示是否有异常,当抛出异常时需要获取Heap_lock。
  5. bool throw_illegal_thread_state = false;
  6. // 在发布jvmti事件之前,必须释放Threads_lock
  7. // in Thread::start.
  8. {
  9. // 获取 Threads_lock锁
  10. MutexLocker mu(Threads_lock);
  11. if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
  12. throw_illegal_thread_state = true;
  13. } else {
  14. // We could also check the stillborn flag to see if this thread was already stopped, but
  15. // for historical reasons we let the thread detect that itself when it starts running
  16. jlong size =
  17. java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
  18. // 创建JVM线程(用JavaThread对象表示)
  19. size_t sz = size > 0 ? (size_t) size : 0;
  20. native_thread = new JavaThread(&thread_entry, sz);
  21. ...
  22. }
  23. }
  24. ...
  25. Thread::start(native_thread);//启动内核线程
  26. JVM_END

源码见:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/prims/jvm.cpp

我们再来看看JavaThread的实现,发现内部通过 os::create_thread(this, thr_type, stack_sz);来调用不同操作系统的创建线程方法创建线程。

  1. JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  2. Thread()
  3. #if INCLUDE_ALL_GCS
  4. , _satb_mark_queue(&_satb_mark_queue_set),
  5. _dirty_card_queue(&_dirty_card_queue_set)
  6. #endif // INCLUDE_ALL_GCS
  7. {
  8. if (TraceThreadEvents) {
  9. tty->print_cr("creating thread %p", this);
  10. }
  11. initialize();
  12. _jni_attach_state = _not_attaching_via_jni;
  13. set_entry_point(entry_point);
  14. // Create the native thread itself.
  15. // %note runtime_23
  16. os::ThreadType thr_type = os::java_thread;
  17. thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
  18. os::java_thread;
  19. os::create_thread(this, thr_type, stack_sz);//调用不同操作系统的创建线程方法创建线程
  20. }

源码见:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/thread.cpp

我们都知道Java是跨平台的,但是native各种方法底层c/c++代码对各平台都需要有对应的兼容,我们这边以linux为例,其他平台就大家自行去查阅了

  1. bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
  2. assert(thread->osthread() == NULL, "caller responsible");
  3. // Allocate the OSThread object
  4. OSThread* osthread = new OSThread(NULL, NULL);
  5. if (osthread == NULL) {
  6. return false;
  7. }
  8. // set the correct thread state
  9. osthread->set_thread_type(thr_type);
  10. // Initial state is ALLOCATED but not INITIALIZED
  11. osthread->set_state(ALLOCATED);
  12. thread->set_osthread(osthread);
  13. // init thread attributes
  14. pthread_attr_t attr;
  15. pthread_attr_init(&attr);
  16. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  17. // stack size
  18. if (os::Linux::supports_variable_stack_size()) {
  19. // calculate stack size if it's not specified by caller
  20. if (stack_size == 0) {
  21. stack_size = os::Linux::default_stack_size(thr_type);
  22. switch (thr_type) {
  23. case os::java_thread:
  24. // Java threads use ThreadStackSize which default value can be
  25. // changed with the flag -Xss
  26. assert (JavaThread::stack_size_at_create() > 0, "this should be set");
  27. stack_size = JavaThread::stack_size_at_create();
  28. break;
  29. case os::compiler_thread:
  30. if (CompilerThreadStackSize > 0) {
  31. stack_size = (size_t)(CompilerThreadStackSize * K);
  32. break;
  33. } // else fall through:
  34. // use VMThreadStackSize if CompilerThreadStackSize is not defined
  35. case os::vm_thread:
  36. case os::pgc_thread:
  37. case os::cgc_thread:
  38. case os::watcher_thread:
  39. if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K);
  40. break;
  41. }
  42. }
  43. stack_size = MAX2(stack_size, os::Linux::min_stack_allowed);
  44. pthread_attr_setstacksize(&attr, stack_size);
  45. } else {
  46. // let pthread_create() pick the default value.
  47. }
  48. // glibc guard page
  49. pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));
  50. ThreadState state;
  51. {
  52. // Serialize thread creation if we are running with fixed stack LinuxThreads
  53. bool lock = os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack();
  54. if (lock) {
  55. os::Linux::createThread_lock()->lock_without_safepoint_check();
  56. }
  57. pthread_t tid;
  58. //通过pthread_create方法创建内核级线程 !
  59. int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
  60. pthread_attr_destroy(&attr);
  61. if (ret != 0) {
  62. if (PrintMiscellaneous && (Verbose || WizardMode)) {
  63. perror("pthread_create()");
  64. }
  65. // Need to clean up stuff we've allocated so far
  66. thread->set_osthread(NULL);
  67. delete osthread;
  68. if (lock) os::Linux::createThread_lock()->unlock();
  69. return false;
  70. }
  71. // Store pthread info into the OSThread
  72. osthread->set_pthread_id(tid);
  73. // Wait until child thread is either initialized or aborted
  74. {
  75. Monitor* sync_with_child = osthread->startThread_lock();
  76. MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
  77. while ((state = osthread->get_state()) == ALLOCATED) {
  78. sync_with_child->wait(Mutex::_no_safepoint_check_flag);
  79. }
  80. }
  81. if (lock) {
  82. os::Linux::createThread_lock()->unlock();
  83. }
  84. }
  85. // Aborted due to thread limit being reached
  86. if (state == ZOMBIE) {
  87. thread->set_osthread(NULL);
  88. delete osthread;
  89. return false;
  90. }
  91. // The thread is returned suspended (in state INITIALIZED),
  92. // and is started higher up in the call chain
  93. assert(state == INITIALIZED, "race condition");
  94. return true;
  95. }

源码见:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/os/linux/vm/os_linux.cpp

主要通过pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread),它是unix 创建线程的方法,linux也继承了。调用后在linux系统中会创建一个内核级的线程。也就是说这个时候操作系统中线程才真正地诞生

更多精彩文章在公众号「小牛呼噜噜

但此时线程才诞生,那是怎么启动的?我们回到JVM_StartThread源码中,Thread::start(native_thread)很明显这行代码就表示启动native_thread = new JavaThread(&thread_entry, sz)创建的线程,我们来继续看看其源码

  1. void Thread::start(Thread* thread) {
  2. trace("start", thread);
  3. // Start is different from resume in that its safety is guaranteed by context or
  4. // being called from a Java method synchronized on the Thread object.
  5. if (!DisableStartThread) {
  6. if (thread->is_Java_thread()) {
  7. // 设置线程状态
  8. java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
  9. java_lang_Thread::RUNNABLE);
  10. }
  11. os::start_thread(thread);
  12. }
  13. }

源码:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/thread.cpp

os::start_thread它封装了pd_start_thread(thread),执行该方法,操作系统会去启动指定的线程

  1. void os::start_thread(Thread* thread) {
  2. // guard suspend/resume
  3. MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);
  4. OSThread* osthread = thread->osthread();
  5. osthread->set_state(RUNNABLE);
  6. pd_start_thread(thread);
  7. }

当操作系统的线程启动完之后,我们再回到pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread),会去java_start这个线程入口函数进行OS内核级线程的初始化,并开始启动JavaThread

  1. // Thread start routine for all newly created threads
  2. static void *java_start(Thread *thread) {
  3. // Try to randomize the cache line index of hot stack frames.
  4. // This helps when threads of the same stack traces evict each other's
  5. // cache lines. The threads can be either from the same JVM instance, or
  6. // from different JVM instances. The benefit is especially true for
  7. // processors with hyperthreading technology.
  8. static int counter = 0;
  9. int pid = os::current_process_id();
  10. alloca(((pid ^ counter++) & 7) * 128);
  11. ThreadLocalStorage::set_thread(thread);
  12. OSThread* osthread = thread->osthread();
  13. Monitor* sync = osthread->startThread_lock();
  14. // non floating stack LinuxThreads needs extra check, see above
  15. if (!_thread_safety_check(thread)) {
  16. // notify parent thread
  17. MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
  18. osthread->set_state(ZOMBIE);
  19. sync->notify_all();
  20. return NULL;
  21. }
  22. // thread_id is kernel thread id (similar to Solaris LWP id)
  23. osthread->set_thread_id(os::Linux::gettid());
  24. if (UseNUMA) {
  25. int lgrp_id = os::numa_get_group_id();
  26. if (lgrp_id != -1) {
  27. thread->set_lgrp_id(lgrp_id);
  28. }
  29. }
  30. // initialize signal mask for this thread
  31. os::Linux::hotspot_sigmask(thread);
  32. // initialize floating point control register
  33. os::Linux::init_thread_fpu_state();
  34. // handshaking with parent thread
  35. {
  36. MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
  37. // notify parent thread
  38. osthread->set_state(INITIALIZED);
  39. sync->notify_all();
  40. // 等待,直到操作系统级线程全部启动
  41. while (osthread->get_state() == INITIALIZED) {
  42. sync->wait(Mutex::_no_safepoint_check_flag);
  43. }
  44. }
  45. // 开始运行JavaThread::run
  46. thread->run();
  47. return 0;
  48. }

源码:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/os/linux/vm/os_linux.cpp

thread->run()其实就是JavaThread::run()也表明方法开始回调,从OS层方法回到JVM层方法

,我们再来看下其实现:

  1. // The first routine called by a new Java thread
  2. void JavaThread::run() {
  3. // initialize thread-local alloc buffer related fields
  4. this->initialize_tlab();
  5. // used to test validitity of stack trace backs
  6. this->record_base_of_stack_pointer();
  7. // Record real stack base and size.
  8. this->record_stack_base_and_size();
  9. // Initialize thread local storage; set before calling MutexLocker
  10. this->initialize_thread_local_storage();
  11. this->create_stack_guard_pages();
  12. this->cache_global_variables();
  13. // Thread is now sufficient initialized to be handled by the safepoint code as being
  14. // in the VM. Change thread state from _thread_new to _thread_in_vm
  15. ThreadStateTransition::transition_and_fence(this, _thread_new, _thread_in_vm);
  16. assert(JavaThread::current() == this, "sanity check");
  17. assert(!Thread::current()->owns_locks(), "sanity check");
  18. DTRACE_THREAD_PROBE(start, this);
  19. // This operation might block. We call that after all safepoint checks for a new thread has
  20. // been completed.
  21. this->set_active_handles(JNIHandleBlock::allocate_block());
  22. if (JvmtiExport::should_post_thread_life()) {
  23. JvmtiExport::post_thread_start(this);
  24. }
  25. JFR_ONLY(Jfr::on_thread_start(this);)
  26. // We call another function to do the rest so we are sure that the stack addresses used
  27. // from there will be lower than the stack base just computed
  28. thread_main_inner();//!!!注意此处方法
  29. // Note, thread is no longer valid at this point!
  30. }
  31. void JavaThread::thread_main_inner() {
  32. assert(JavaThread::current() == this, "sanity check");
  33. assert(this->threadObj() != NULL, "just checking");
  34. // Execute thread entry point unless this thread has a pending exception
  35. // or has been stopped before starting.
  36. // Note: Due to JVM_StopThread we can have pending exceptions already!
  37. if (!this->has_pending_exception() &&
  38. !java_lang_Thread::is_stillborn(this->threadObj())) {
  39. {
  40. ResourceMark rm(this);
  41. this->set_native_thread_name(this->get_thread_name());
  42. }
  43. HandleMark hm(this);
  44. this->entry_point()(this, this);//JavaThread对象中传入的entry_point为Thread对象的Thread::run方法
  45. }
  46. DTRACE_THREAD_PROBE(stop, this);
  47. this->exit(false);
  48. delete this;
  49. }

源码:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/thread.cpp

由于JavaThread定义可知JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz)中参数entry_point是外部传入,那我们想想JavaThread是什么时候实例化的?

没错,就是我们一开始的JVM_StartThreadnative_thread = new JavaThread(&thread_entry, sz);

也就是说this->entry_point()(this, this)实际上是回调的thread_entry方法

thread_entry源码:

  1. static void thread_entry(JavaThread* thread, TRAPS) {
  2. HandleMark hm(THREAD);
  3. Handle obj(THREAD, thread->threadObj());
  4. JavaValue result(T_VOID);
  5. JavaCalls::call_virtual(&result,
  6. obj,
  7. KlassHandle(THREAD, SystemDictionary::Thread_klass()),
  8. vmSymbols::run_method_name(),
  9. vmSymbols::void_method_signature(),
  10. THREAD);
  11. }

源码:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/prims/jvm.cpp

通过JavaCalls::call_virtual方法,又从JVM层 回到了Java语言层 ,即MyThread thread = new MyThread(); thread.start();

一切又回到了起点,这就是Javathread.start()内部完整的一个流程,HotSpot虚拟机实现的Java线程其实是对Linux内核级线程的直接映射,将Java涉及到的所有线程调度、内存分配都交由操作系统进行管理

线程终止状态

线程终止状态(TERMINATED),表示该线程已经运行完毕。

当一个线程执行完毕,或者主线程的main()方法完成时,我们就认为它终止了。终止的线程无法在被使用,如果调用start()方法,会抛出java.lang.IllegalThreadStateException异常,这一点我们可以从start源码中很容易地得到

  1. public synchronized void start() {
  2. if (threadStatus != 0)
  3. throw new IllegalThreadStateException();
  4. ...
  5. }

线程阻塞状态

线程阻塞状态(BLOCKED),需要等待锁释放或者说获取锁失败时,线程阻塞

  1. public class BlockedThread implements Runnable {
  2. @Override
  3. public void run() {
  4. synchronized (BlockedThread.class){
  5. while (true){
  6. }
  7. }
  8. }
  9. }

从Thread源码的注释中,我们可以知道等待锁释放或者说获取锁失败,主要有下面3中情况:

  1. 进入 synchronized 方法时
  2. 进入 synchronized 块时
  3. 调用 wait 后, 重新进入 synchronized 方法/块时

其中第三种情况,大家可以先思考一下,我们留在下文线程等待状态再详细展开

线程等待状态

线程等待状态(WAITING),表示该线程需要等待其他线程做出一些特定动作(通知或中断)。

wait/notify/notifyAll

我们紧接着上一小节,调用 wait 后, 重新进入synchronized 方法/块时,我们来看看期间发生了什么?

线程1调用对象A的wait方法后,会释放当前的锁,然后让出CPU时间片,线程会进入该对象的等待队列中,线程状态变为 等待状态WAITING

当另一个线程2调用了对象A的notify()/notifyAll()方法

notify()方法只会唤醒沉睡的线程,不会立即释放之前占有的对象A的锁,必须执行完notify()方法所在的synchronized代码块后才释放。所以在编程中,尽量在使用了notify/notifyAll()后立即退出临界区

线程1收到通知后退出等待队列,并进入线程运行状态RUNNABLE,等待 CPU 时间片分配, 进而执行后续操作,接着线程1重新进入 synchronized 方法/块时,竞争不到锁,线程状态变为线程阻塞状态BLOCKED。如果竞争到锁,就直接接着运行。线程等待状态 切换到线程阻塞状态,无法直接切换,需要经过线程运行状态。

我们再来看一个例子,巩固巩固:

  1. public class WaitNotifyTest {
  2. public static void main(String[] args) {
  3. Object A = new Object();
  4. new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. System.out.println("线程1等待获取 对象A的锁...");
  8. synchronized (A) {
  9. try {
  10. System.out.println("线程1获取了 对象A的锁");
  11. Thread.sleep(3000);
  12. System.out.println("线程1开始运行wait()方法进行等待,进入到等待队列......");
  13. A.wait();
  14. System.out.println("线程1等待结束");
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }
  20. }).start();
  21. new Thread(new Runnable() {
  22. @Override
  23. public void run() {
  24. System.out.println("线程2等待获取 对象A的锁...");
  25. synchronized (A) {
  26. System.out.println("线程2获取了 对象A的锁");
  27. try {
  28. Thread.sleep(3000);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. System.out.println("线程2将要运行notify()方法进行唤醒线程1");
  33. A.notify();
  34. }
  35. }
  36. }).start();
  37. }
  38. }

结果:

  1. 线程1等待获取 对象A的锁...
  2. 线程1获取了 对象A的锁
  3. 线程2等待获取 对象A的锁...
  4. 线程1开始运行wait()方法进行等待,进入到等待队列......
  5. 线程2获取了 对象A的锁
  6. 线程2将要运行notify()方法进行唤醒线程1
  7. 线程1等待结束

需要注意的是,wait/notify/notifyAll 只能在synchronized修饰的方法、块中使用notify 是只随机唤醒一个线程,而 notifyAll 是唤醒所有等待队列中的线程

join

Thread类中的join方法的主要作用能让线程之间的并行执行变为串行执行,当前线程等该加入该线程后面,等待该线程终止

  1. public static void main(String[] args) {
  2. Thread thread = new Thread();
  3. thread.start();
  4. thread.join();
  5. ...
  6. }

上面一个例子表示,程序在main主线程中调用thread线程的join方法,意味着main线程放弃CPU时间片(主线程会变成 WAITING 状态),并返回thread线程,继续执行直到线程thread执行完毕,换句话说在主线程执行过程中,插入thread线程,还得等thread线程执行完后,才轮到主线程继续执行

如果查看JDKthread.join()底层实现,会发现其实内部封装了wait(),notifyAll()

park/unpark

LockSupport.park() 挂起当前线程;LockSupport.unpark(暂停线程对象) 恢复某个线程

  1. package com.zj.ideaprojects.demo.test3;
  2. import java.util.concurrent.Executors;
  3. import java.util.concurrent.locks.LockSupport;
  4. public class ThreadLockSupportTest {
  5. public static void main(String[] args) throws InterruptedException {
  6. Thread thread = new Thread(() -> {
  7. System.out.println("start.....");
  8. try {
  9. Thread.sleep(1000);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println("park....");
  14. LockSupport.park();
  15. System.out.println("resume.....");
  16. });
  17. thread.start();
  18. Thread.sleep(3000);
  19. System.out.println("unpark....");
  20. LockSupport.unpark(thread);
  21. }
  22. }

结果:

  1. start.....
  2. park....
  3. unpark....
  4. resume.....

当程序调用LockSupport.park(),会让当前线程A的线程状态会从 RUNNABLE 变成 WAITING,然后main主线程调用LockSupport.unpark(thread),让指定的线程即线程A,从 WAITING 回到 RUNNABLE 。我们可以发现

park/unparkwait/notify/notifyAll很像,但是他们有以下的区别:

  1. wait,notify 和 notifyAll 必须事先获取对象锁,而 unpark 不必
  2. park、unpark 可以先 unpark ,而 wait、notify 不能先 notify,必须先wait
  3. unpark 可以精准唤醒某一个确定的线程。而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所以等待线程,就不那么精确

超时等待状态

超时等待状态(TIMED_WAITING),也叫限期等待,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。

这部分比较简单,它和线程等待状态(WAITING)状态 非常相似,区别就是方法的参数舒服传入限制时间,在 Timed Waiting状态时会等待超时,之后由系统唤醒,或者也可以提前被通知唤醒如 notify

相关方法主要有:

  1. 1. Object.wait(long)
  2. 2. Thread.join(long)
  3. 3. LockSupport.parkNanos(long)
  4. 4. LockSupport.parkUntil(long)
  5. 5. Thread.sleep(long)

需要注意的是Thread.sleep(long),当线程执行sleep方法时,不会释放当前的锁(如果当前线程进入了同步锁),也不会让出CPU。sleep(long)可以用指定时间使它自动唤醒过来,如果时间不到只能调用interrupt方法强行打断。

参考资料:

https://hg.openjdk.java.net/jdk8u

《并发编程的艺术》

https://www.jianshu.com/p/216a41352fd8


本篇文章到这里就结束啦,如果我的文章对你有所帮助,还请帮忙一键三连:点赞、关注、收藏,你的支持会激励我输出更高质量的文章,感谢!

原文镜像:原来还能这样看Java线程的状态及转换

计算机内功、源码解析、科技故事、项目实战、面试八股等更多硬核文章,首发于公众号「小牛呼噜噜」,我们下期再见!

原来还能这样看Java线程的状态及转换的更多相关文章

  1. Java线程的状态

    Java线程的状态 线程对象在不同的运行时期有不同的状态,状态信息就存在于Thread中的State枚举中,如下所示: public enum State { /** * 至今尚未启动的线程处于这种状 ...

  2. 从源码看java线程状态

    关于java线程状态,网上查资料很混乱,有的说5种状态,有的说6种状态,初学者搞不清楚这个线程状态到底是怎么样的,今天我讲一下如何看源码去解决这个疑惑. 直接上代码: public class Thr ...

  3. Java 线程的状态

    Java Thread的运行周期中, 有几种状态, 在 java.lang.Thread.State 中有详细定义和说明: NEW 状态是指线程刚创建, 尚未启动 RUNNABLE 状态是线程正在正常 ...

  4. Thread线程源码解析,Java线程的状态,线程之间的通信

    线程的基本概念 什么是线程 现代操作系统在运行一个程序的时候,会为其创建一个进程.例如,启动一个Java程序,操作系统就会创建一个Java进程.线代操作系统调度的最小单位是线程.也叫做轻量级进程.在一 ...

  5. Java线程池状态和状态切换

    摘要 介绍线程池的五种状态RUNNING.SHUTDOWN.STOP.TIDYING和TERMINATED,并简述五种状态之间的切换.   在类ThreadPoolExecutor中定义了一个成员变量 ...

  6. java多线程基础(二)--java线程各状态关系

    注意只有可运行(就绪态)和运行中(运行态)可以相互转换

  7. Java线程池使用和常用参数

    多线程问题: 1.java中为什么要使用多线程使用多线程,可以把一些大任务分解成多个小任务来执行,多个小任务之间互不影像,同时进行,这样,充分利用了cpu资源. 2.java中简单的实现多线程的方式 ...

  8. Java问题定位之Java线程堆栈分析

    采用Java开发的大型应用系统越来越大,越来越复杂,很多系统集成在一起,整个系统看起来像个黑盒子.系统运行遭遇问题(系统停止响应,运行越来越慢,或者性能低下,甚至系统宕掉),如何速度命中问题的根本原因 ...

  9. 面试官:都说阻塞 I/O 模型将会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?

    摘要: 原创出处 https://studyidea.cn 「公众号:程序通事 」欢迎关注和转载,保留摘要,谢谢! 使用 Java 阻塞 I/O 模型读取数据,将会导致线程阻塞,线程将会进入休眠,从而 ...

  10. 【Java并发基础】Java线程的生命周期

    前言 线程是操作系统中的一个概念,支持多线程的语言都是对OS中的线程进行了封装.要学好线程,就要搞清除它的生命周期,也就是生命周期各个节点的状态转换机制.不同的开发语言对操作系统中的线程进行了不同的封 ...

随机推荐

  1. vi/vim 命令

    vim 文件路径 编辑一个文件,英文模式, 按i:输入模式 按Esc:命令模式 输入模式 dd 删除一行 gg 跳到开头 shift+g 跳到结尾 U 撤销 shift+U 恢复撤销 命令模式 :wq ...

  2. go语言的特性

    一.golang语言特性 1. 垃圾回收 a.内存自动回收,再也不需要开发人员管理内存  //开发代码中不能存在无引用的变量,不然代码出错 b.开发人员专注业务实现,降低了心智负担 c.只需要new分 ...

  3. 2月22日javaweb学习之Maven

    Maveb是专门用于管理和构建java项目的工具,它的主要功能有: 1.提供一套标准化的项目结构. 2.提供一套标准化的构建流程(编译.测试.打包.发布......) 3.提供了一套依赖管理机制 Ma ...

  4. cesium 入门指南

    最近拿到了几份offer,经过这次找工作发现自己最近脱节挺严重,为了后续的职业发展,决定开始书写博客记录自己的努力. cesium属于 跨平台.跨浏览器的展现三维地球.地图的JavaScript库. ...

  5. 位运算与MOD快速幂详细知识点

    最近写的一些题目设计到了数论的取模 如下题 链接:https://ac.nowcoder.com/acm/contest/3003/G来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制: ...

  6. 利用shell脚本提高访问GitHub速度

    Github由于做了域名限制,所以访问比较慢,编写了个脚本达到做本地域名解析提高GitHub的访问速度 #!/usr/bin/env bash # 该脚本用来提升github的访问速度 ROOT_UI ...

  7. github 开源项目安装

    1.打开github 2.搜索相关项目 3.使用系统下载安装 4.执行 指令;git clone git://github.com/****.git

  8. tensorflow的断点续训

    tensorflow的断点续训 2019-09-07 顾名思义,断点续训的意思是因为某些原因模型还没有训练完成就被中断,下一次训练可以在上一次训练的基础上继续训练而不用从头开始:这种方式对于你那些训练 ...

  9. antd动态tree 自定义样式

    import React, { useEffect, useState } from 'react';import { Tree } from 'antd';import './index.less' ...

  10. 5G如何加速无人快递?5G智能网关新应用

    网上购物已经是现代生活的主流消费方式之一,伴随网购的繁荣,物流快递行业也进入到一个最火热的时期.而在这之中,有限的快递配送能力和日益增长的配送需求的矛盾持续凸显,因此无人快递车一类的创新应用也应运而生 ...