日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

大家好,我是呼嚕嚕,最近一直在梳理JAVA并發(fā),但內(nèi)容雜且偏晦澀,今天我們一起來聊聊Java 線程的狀態(tài)及轉(zhuǎn)換 先來夯實一下基礎(chǔ),萬丈高樓平地起,路還是得慢慢走。

Java線程的生命周期

我們先來看下Java線程的生命周期圖:

 

上圖也是本文的大綱,我們下面依次聊聊java各個線程狀態(tài)及其他們的轉(zhuǎn)換。

線程初始狀態(tài)

線程初始狀態(tài)(NEW): 當前線程處于線程被創(chuàng)建出來但沒有被調(diào)用start()

在Java線程的時間中,關(guān)于線程的一切的起點是從Thread 類的對象的創(chuàng)建開始,一般實現(xiàn)Runnable接口 或者 繼承Thread類的類,實例化一個對象出來,線程就進入了初始狀態(tài)

Thread thread = new Thread()

由于線程在我們操作系統(tǒng)中也是非常寶貴的資源,在實際開發(fā)中,我們常常用線程池來重復利用現(xiàn)有的線程來執(zhí)行任務,避免多次創(chuàng)建和銷毀線程,從而降低創(chuàng)建和銷毀線程過程中的代價。Java 給我們提供了 Executor 接口來使用線程池,查看其JDK1.8源碼,發(fā)現(xiàn)其內(nèi)部封裝了Thread t = new Thread()

public class Executors {
    ...
  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;

        ...

        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;
        }
    }
    ...
}

在thread類源碼中,我們還能發(fā)現(xiàn)線程狀態(tài)的枚舉類State:

    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        RUNNABLE,

        BLOCKED,

        WAITING,

        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

所謂線程的狀態(tài),在java源碼中都是通過threadStatus的值來表示的

   /* Java thread status for tools,
     * initialized to indicate thread 'not yet started'
     */

    private volatile int threadStatus = 0;

State 和 threadStatus 通過toThreadState方法映射轉(zhuǎn)換

    public State getState() {
        // get current thread state
        return sun.misc.VM.toThreadState(threadStatus);
    }

//--- --- ---

    public static State toThreadState(int var0) {
        if ((var0 & 4) != 0) {
            return State.RUNNABLE;
        } else if ((var0 & 1024) != 0) {
            return State.BLOCKED;
        } else if ((var0 & 16) != 0) {
            return State.WAITING;
        } else if ((var0 & 32) != 0) {
            return State.TIMED_WAITING;
        } else if ((var0 & 2) != 0) {
            return State.TERMINATED;
        } else {
            return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
        }
    }

到這里我們就可以發(fā)現(xiàn),Thread t = new Thread()在Java中只是設置了線程的狀態(tài),操作系統(tǒng)中并沒有的實際線程的創(chuàng)建

線程運行狀態(tài)

線程運行狀態(tài)(RUNNABLE),線程被調(diào)用了start()等待運行的狀態(tài)

在linux操作系統(tǒng)層面,包含Running和 Ready狀態(tài)。其中Ready狀態(tài)是等待 CPU 時間片。現(xiàn)今主流的JVM,比如hotspot虛擬機都是把Java 線程,映射到操作系統(tǒng)OS底層的線程上,把調(diào)度委托給了操作系統(tǒng)。而操作系統(tǒng)比如Linux,它是多任務操作系統(tǒng),充分利用CPU的高性能,將CPU的時間分片,讓單個CPU實現(xiàn)"同時執(zhí)行"多任務的效果。

Linux的任務調(diào)度又采用搶占式輪轉(zhuǎn)調(diào)度,我們不考慮特權(quán)進程的話OS會選擇在CPU上占用的時間最少進程,優(yōu)先在cpu上分配資源,其對應的線程去執(zhí)行任務,盡可能地維護任務調(diào)度公平。Running和 Ready狀態(tài)的線程在CPU中切換狀態(tài)非常短暫。大概只有 0.01 秒這一量級,區(qū)分開來意義不大,java將這2個狀態(tài)統(tǒng)一用RUNNABLE來表示

thread.start()源碼解析

我們接下來看看為什么說執(zhí)行thread.start()后,線程的才"真正的創(chuàng)建"

public class ThreadTest {
    /**
     * 繼承Thread類
     */
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("This is child thread");
        }
    }
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

其中thread.start()方法的源碼中,會去調(diào)用start0()方法,而start0()是private native void start0();JVM調(diào)用Native方法的話,會進入到不受JVM控制的世界里

在Thread類實例化的同時,會首先調(diào)用registerNatives方法,注冊本地Native方法,動態(tài)綁定JVM方法

private static native void registerNatives();
    static {
        registerNatives();
    }

在Thread類中通過registerNatives將指定的本地方法綁定到指定函數(shù),比如start0本地方法綁定到JVM_StartThread函數(shù):

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

源碼見:
http://hg.openjdk.java.NET/jdk8u/jdk8u60/jdk/file/935758609767/src/share/native/java/lang/Thread.c

JVM_StartThread是JVM層函數(shù),拋去各種情況的處理,主要是通過new JavaThread(&thread_entry, sz)來創(chuàng)建JVM線程對象


JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrApper("JVM_StartThread");
  JavaThread *native_thread = NULL;

    //表示是否有異常,當拋出異常時需要獲取Heap_lock。
  bool throw_illegal_thread_state = false;

  // 在發(fā)布jvmti事件之前,必須釋放Threads_lock
  // in Thread::start.
  {
    // 獲取 Threads_lock鎖
    MutexLocker mu(Threads_lock);


    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {
      // We could also check the stillborn flag to see if this thread was already stopped, but
      // for historical reasons we let the thread detect that itself when it starts running

      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
      
        // 創(chuàng)建JVM線程(用JavaThread對象表示)
      size_t sz = size > 0 ? (size_t) size : 0;
      native_thread = new JavaThread(&thread_entry, sz);
      ...
    }
  }

  ...

  Thread::start(native_thread);//啟動內(nèi)核線程

JVM_END

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

我們再來看看JavaThread的實現(xiàn),發(fā)現(xiàn)內(nèi)部通過 os::create_thread(this, thr_type, stack_sz);來調(diào)用不同操作系統(tǒng)的創(chuàng)建線程方法創(chuàng)建線程。

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  Thread()
#if INCLUDE_ALL_GCS
  , _satb_mark_queue(&_satb_mark_queue_set),
  _dirty_card_queue(&_dirty_card_queue_set)
#endif // INCLUDE_ALL_GCS
{
  if (TraceThreadEvents) {
    tty->print_cr("creating thread %p", this);
  }
  initialize();
  _jni_attach_state = _not_attaching_via_jni;
  set_entry_point(entry_point);
  // Create the native thread itself.
  // %note runtime_23
  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);//調(diào)用不同操作系統(tǒng)的創(chuàng)建線程方法創(chuàng)建線程

}

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

我們都知道Java是跨平臺的,但是native各種方法底層c/c++代碼對各平臺都需要有對應的兼容,我們這邊以linux為例,其他平臺就大家自行去查閱了

bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
  assert(thread->osthread() == NULL, "caller responsible");

  // Allocate the OSThread object
  OSThread* osthread = new OSThread(NULL, NULL);
  if (osthread == NULL) {
    return false;
  }

  // set the correct thread state
  osthread->set_thread_type(thr_type);

  // Initial state is ALLOCATED but not INITIALIZED
  osthread->set_state(ALLOCATED);

  thread->set_osthread(osthread);

  // init thread attributes
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

  // stack size
  if (os::Linux::supports_variable_stack_size()) {
    // calculate stack size if it's not specified by caller
    if (stack_size == 0) {
      stack_size = os::Linux::default_stack_size(thr_type);

      switch (thr_type) {
      case os::java_thread:
        // Java threads use ThreadStackSize which default value can be
        // changed with the flag -Xss
        assert (JavaThread::stack_size_at_create() > 0, "this should be set");
        stack_size = JavaThread::stack_size_at_create();
        break;
      case os::compiler_thread:
        if (CompilerThreadStackSize > 0) {
          stack_size = (size_t)(CompilerThreadStackSize * K);
          break;
        } // else fall through:
          // use VMThreadStackSize if CompilerThreadStackSize is not defined
      case os::vm_thread:
      case os::pgc_thread:
      case os::cgc_thread:
      case os::watcher_thread:
        if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K);
        break;
      }
    }

    stack_size = MAX2(stack_size, os::Linux::min_stack_allowed);
    pthread_attr_setstacksize(&attr, stack_size);
  } else {
    // let pthread_create() pick the default value.
  }

  // glibc guard page
  pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));

  ThreadState state;

  {
    // Serialize thread creation if we are running with fixed stack LinuxThreads
    bool lock = os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack();
    if (lock) {
      os::Linux::createThread_lock()->lock_without_safepoint_check();
    }

    pthread_t tid;
      //通過pthread_create方法創(chuàng)建內(nèi)核級線程 !
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

    pthread_attr_destroy(&attr);

    if (ret != 0) {
      if (PrintMiscellaneous && (Verbose || WizardMode)) {
        perror("pthread_create()");
      }
      // Need to clean up stuff we've allocated so far
      thread->set_osthread(NULL);
      delete osthread;
      if (lock) os::Linux::createThread_lock()->unlock();
      return false;
    }

    // Store pthread info into the OSThread
    osthread->set_pthread_id(tid);

    // Wait until child thread is either initialized or aborted
    {
      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);
      }
    }

    if (lock) {
      os::Linux::createThread_lock()->unlock();
    }
  }

  // Aborted due to thread limit being reached
  if (state == ZOMBIE) {
      thread->set_osthread(NULL);
      delete osthread;
      return false;
  }

  // The thread is returned suspended (in state INITIALIZED),
  // and is started higher up in the call chain
  assert(state == INITIALIZED, "race condition");
  return true;
}

源碼見:
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 創(chuàng)建線程的方法,linux也繼承了。調(diào)用后在linux系統(tǒng)中會創(chuàng)建一個內(nèi)核級的線程。也就是說這個時候操作系統(tǒng)中線程才真正地誕生

但此時線程才誕生,那是怎么啟動的?我們回到JVM_StartThread源碼中,Thread::start(native_thread)很明顯這行代碼就表示啟動native_thread = new JavaThread(&thread_entry, sz)創(chuàng)建的線程,我們來繼續(xù)看看其源碼

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()) {
      // 設置線程狀態(tài)
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    os::start_thread(thread);
  }
}

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

os::start_thread它封裝了pd_start_thread(thread),執(zhí)行該方法,操作系統(tǒng)會去啟動指定的線程

void os::start_thread(Thread* thread) {
  // guard suspend/resume
  MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);
  OSThread* osthread = thread->osthread();
  osthread->set_state(RUNNABLE);
  pd_start_thread(thread);
}

當操作系統(tǒng)的線程啟動完之后,我們再回到pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread),會去java_start這個線程入口函數(shù)進行OS內(nèi)核級線程的初始化,并開始啟動JavaThread

// Thread start routine for all newly created threads
static void *java_start(Thread *thread) {
  // Try to randomize the cache line index of hot stack frames.
  // This helps when threads of the same stack traces evict each other's
  // cache lines. The threads can be either from the same JVM instance, or
  // from different JVM instances. The benefit is especially true for
  // processors with hyperthreading technology.
  static int counter = 0;
  int pid = os::current_process_id();
  alloca(((pid ^ counter++) & 7) * 128);

  ThreadLocalStorage::set_thread(thread);

  OSThread* osthread = thread->osthread();
  Monitor* sync = osthread->startThread_lock();

  // non floating stack LinuxThreads needs extra check, see above
  if (!_thread_safety_check(thread)) {
    // notify parent thread
    MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
    osthread->set_state(ZOMBIE);
    sync->notify_all();
    return NULL;
  }

  // thread_id is kernel thread id (similar to Solaris LWP id)
  osthread->set_thread_id(os::Linux::gettid());

  if (UseNUMA) {
    int lgrp_id = os::numa_get_group_id();
    if (lgrp_id != -1) {
      thread->set_lgrp_id(lgrp_id);
    }
  }
  // initialize signal mask for this thread
  os::Linux::hotspot_sigmask(thread);

  // initialize floating point control register
  os::Linux::init_thread_fpu_state();

  // handshaking with parent thread
  {
    MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);

    // notify parent thread
    osthread->set_state(INITIALIZED);
    sync->notify_all();

    // 等待,直到操作系統(tǒng)級線程全部啟動
    while (osthread->get_state() == INITIALIZED) {
      sync->wait(Mutex::_no_safepoint_check_flag);
    }
  }

  // 開始運行JavaThread::run
  thread->run();

  return 0;
}

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

thread->run()其實就是JavaThread::run()也表明方法開始回調(diào),從OS層方法回到JVM層方法 ,我們再來看下其實現(xiàn):

// The first routine called by a new Java thread
void JavaThread::run() {
  // initialize thread-local alloc buffer related fields
  this->initialize_tlab();

  // used to test validitity of stack trace backs
  this->record_base_of_stack_pointer();

  // Record real stack base and size.
  this->record_stack_base_and_size();

  // Initialize thread local storage; set before calling MutexLocker
  this->initialize_thread_local_storage();

  this->create_stack_guard_pages();

  this->cache_global_variables();

  // Thread is now sufficient initialized to be handled by the safepoint code as being
  // in the VM. Change thread state from _thread_new to _thread_in_vm
  ThreadStateTransition::transition_and_fence(this, _thread_new, _thread_in_vm);

  assert(JavaThread::current() == this, "sanity check");
  assert(!Thread::current()->owns_locks(), "sanity check");

  DTRACE_THREAD_PROBE(start, this);

  // This operation might block. We call that after all safepoint checks for a new thread has
  // been completed.
  this->set_active_handles(JNIHandleBlock::allocate_block());

  if (JvmtiExport::should_post_thread_life()) {
    JvmtiExport::post_thread_start(this);
  }

  JFR_ONLY(Jfr::on_thread_start(this);)

  // We call another function to do the rest so we are sure that the stack addresses used
  // from there will be lower than the stack base just computed
  thread_main_inner();//!!!注意此處方法

  // Note, thread is no longer valid at this point!
}

void JavaThread::thread_main_inner() {
  assert(JavaThread::current() == this, "sanity check");
  assert(this->threadObj() != NULL, "just checking");

  // Execute thread entry point unless this thread has a pending exception
  // or has been stopped before starting.
  // Note: Due to JVM_StopThread we can have pending exceptions already!
  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);//JavaThread對象中傳入的entry_point為Thread對象的Thread::run方法
  }

  DTRACE_THREAD_PROBE(stop, this);

  this->exit(false);
  delete this;
}

源碼:
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)中參數(shù)entry_point是外部傳入,那我們想想JavaThread是什么時候?qū)嵗模?沒錯,就是我們一開始的JVM_StartThread中native_thread = new JavaThread(&thread_entry, sz); 也就是說this->entry_point()(this, this)實際上是回調(diào)的thread_entry方法

thread_entry源碼:

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()),
                          vmSymbols::run_method_name(),
                          vmSymbols::void_method_signature(),
                          THREAD);
}

源碼:
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()內(nèi)部完整的一個流程,HotSpot虛擬機實現(xiàn)的Java線程其實是對Linux內(nèi)核級線程的直接映射,將Java涉及到的所有線程調(diào)度、內(nèi)存分配都交由操作系統(tǒng)進行管理

 

線程終止狀態(tài)

線程終止狀態(tài)(TERMINATED),表示該線程已經(jīng)運行完畢。

當一個線程執(zhí)行完畢,或者主線程的main()方法完成時,我們就認為它終止了。終止的線程無法在被使用,如果調(diào)用start()方法,會拋出
java.lang.IllegalThreadStateException異常,這一點我們可以從start源碼中很容易地得到

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    ...
}

線程阻塞狀態(tài)

線程阻塞狀態(tài)(BLOCKED),需要等待鎖釋放或者說獲取鎖失敗時,線程阻塞

public class BlockedThread implements Runnable {
    @Override
    public void run() {
        synchronized (BlockedThread.class){
            while (true){
                
            }
        }
    }
}

從Thread源碼的注釋中,我們可以知道等待鎖釋放或者說獲取鎖失敗,主要有下面3中情況:

  1. 進入 synchronized 方法時
  2. 進入 synchronized 塊時
  3. 調(diào)用 wait 后, 重新進入 synchronized 方法/塊時

其中第三種情況,大家可以先思考一下,我們留在下文線程等待狀態(tài)再詳細展開

線程等待狀態(tài)

線程等待狀態(tài)(WAITING),表示該線程需要等待其他線程做出一些特定動作(通知或中斷)。

wait/notify/notifyAll

我們緊接著上一小節(jié),調(diào)用wait 后, 重新進入synchronized 方法/塊時,我們來看看期間發(fā)生了什么?

線程1調(diào)用對象A的wait方法后,會釋放當前的鎖,然后讓出CPU時間片,線程會進入該對象的等待隊列中,線程狀態(tài)變?yōu)?等待狀態(tài)WAITING。 當另一個線程2調(diào)用了對象A的notify()/notifyAll()方法

notify()方法只會喚醒沉睡的線程,不會立即釋放之前占有的對象A的鎖,必須執(zhí)行完notify()方法所在的synchronized代碼塊后才釋放。所以在編程中,盡量在使用了notify/notifyAll()后立即退出臨界區(qū)

線程1收到通知后退出等待隊列,并進入線程運行狀態(tài)RUNNABLE,等待 CPU 時間片分配, 進而執(zhí)行后續(xù)操作,接著線程1重新進入 synchronized 方法/塊時,競爭不到鎖,線程狀態(tài)變?yōu)榫€程阻塞狀態(tài)BLOCKED。如果競爭到鎖,就直接接著運行。線程等待狀態(tài) 切換到線程阻塞狀態(tài),無法直接切換,需要經(jīng)過線程運行狀態(tài)。

我們再來看一個例子,鞏固鞏固:

public class WaitNotifyTest {
    public static void main(String[] args) {
        Object A = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("線程1等待獲取 對象A的鎖...");
                synchronized (A) {
                    try {
                        System.out.println("線程1獲取了 對象A的鎖");
                        Thread.sleep(3000);
                        System.out.println("線程1開始運行wait()方法進行等待,進入到等待隊列......");
                        A.wait();
                        System.out.println("線程1等待結(jié)束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("線程2等待獲取 對象A的鎖...");
                synchronized (A) {
                    System.out.println("線程2獲取了 對象A的鎖");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("線程2將要運行notify()方法進行喚醒線程1");
                    A.notify();
                }
            }
        }).start();
    }
}

結(jié)果:

線程1等待獲取 對象A的鎖...
線程1獲取了 對象A的鎖
線程2等待獲取 對象A的鎖...
線程1開始運行wait()方法進行等待,進入到等待隊列......
線程2獲取了 對象A的鎖
線程2將要運行notify()方法進行喚醒線程1
線程1等待結(jié)束

需要注意的是,wait/notify/notifyAll 只能在synchronized修飾的方法、塊中使用, notify 是只隨機喚醒一個線程,而 notifyAll 是喚醒所有等待隊列中的線程

join

Thread類中的join方法的主要作用能讓線程之間的并行執(zhí)行變?yōu)榇袌?zhí)行,當前線程等該加入該線程后面,等待該線程終止

public static void main(String[] args) {
  Thread thread = new Thread();
  thread.start();
  thread.join();
  ...
}

上面一個例子表示,程序在main主線程中調(diào)用thread線程的join方法,意味著main線程放棄CPU時間片(主線程會變成 WAITING 狀態(tài)),并返回thread線程,繼續(xù)執(zhí)行直到線程thread執(zhí)行完畢,換句話說在主線程執(zhí)行過程中,插入thread線程,還得等thread線程執(zhí)行完后,才輪到主線程繼續(xù)執(zhí)行

如果查看JDKthread.join()底層實現(xiàn),會發(fā)現(xiàn)其實內(nèi)部封裝了wait(),notifyAll()

park/unpark

LockSupport.park() 掛起當前線程;LockSupport.unpark(暫停線程對象) 恢復某個線程

package com.zj.ideaprojects.demo.test3;

import java.util.concurrent.Executors;
import java.util.concurrent.locks.LockSupport;

public class ThreadLockSupportTest {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("start.....");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("park....");
            LockSupport.park();
            System.out.println("resume.....");

        });
        thread.start();
        Thread.sleep(3000);
        System.out.println("unpark....");
        LockSupport.unpark(thread);

    }
}

結(jié)果:

start.....
park....
unpark....
resume.....

當程序調(diào)用LockSupport.park(),會讓當前線程A的線程狀態(tài)會從 RUNNABLE 變成 WAITING,然后main主線程調(diào)用LockSupport.unpark(thread),讓指定的線程即線程A,從 WAITING 回到 RUNNABLE 。我們可以發(fā)現(xiàn) park/unpark和wait/notify/notifyAll很像,但是他們有以下的區(qū)別:

  1. wait,notify 和 notifyAll 必須事先獲取對象鎖,而 unpark 不必
  2. park、unpark 可以先 unpark ,而 wait、notify 不能先 notify,必須先wait
  3. unpark 可以精準喚醒某一個確定的線程。而 notify 只能隨機喚醒一個等待線程,notifyAll 是喚醒所以等待線程,就不那么精確

超時等待狀態(tài)

超時等待狀態(tài)(TIMED_WAITING),也叫限期等待,可以在指定的時間后自行返回而不是像 WAITING 那樣一直等待。

這部分比較簡單,它和線程等待狀態(tài)(WAITING)狀態(tài) 非常相似,區(qū)別就是方法的參數(shù)舒服傳入限制時間,在 Timed Waiting狀態(tài)時會等待超時,之后由系統(tǒng)喚醒,或者也可以提前被通知喚醒如 notify

相關(guān)方法主要有:

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

需要注意的是Thread.sleep(long),當線程執(zhí)行sleep方法時,不會釋放當前的鎖(如果當前線程進入了同步鎖),也不會讓出CPU。sleep(long)可以用指定時間使它自動喚醒過來,如果時間不到只能調(diào)用interrupt方法強行打斷。

參考資料:
https://hg.openjdk.java.net/jdk8u 《并發(fā)編程的藝術(shù)》 https://www.jianshu.com/p/216a41352fd8


全文完,感謝您的閱讀,如果我的文章對你有所幫助的話,還請點個免費的,你的支持會激勵我輸出更高質(zhì)量的文章,感謝!

原文鏡像:
https://mp.weixin.qq.com/s/YcRaiLAAUDom2Kaq3Yw1Cw

計算機內(nèi)功、源碼解析、科技故事、項目實戰(zhàn)、面試八股等更多硬核文章,首發(fā)于公眾號「小牛呼嚕嚕」,我們下期再見!

分享到:
標簽:線程 Java
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定