Java多線程-線程狀態

概述

最近開始強迫自己養成學習做筆記的習慣,今天重新復習了一下JAVA線程的狀態,記錄一下自己學習到的東西

JAVA線程狀態

根據自己的理解,繪畫了一張圖來描述線程的狀態變化



下面結合線程狀態變化圖進行簡單講解:

  • 調用start()方法,創建線程,等待獲得CPU。
  • 執行同步塊,多個線程競爭鎖,沒有獲得鎖的線程阻塞,等待鎖的釋放
  • 調用Thread.sleep() 方法,線程有限期等待(如果該線程之前獲得鎖,鎖不會被釋放)
  • 調用wait()方法,線程無限期等待,直到其他線程喚醒,當線程調用wait()方法時,線程進入一個阻塞隊列,在后面會具體分析
  • 當線程退出run()方法,線程結束,關于線程的停止在下面的小節會詳細講解。

深入wait()

前面說調用wait()方法以后,線程會進入一個阻塞隊列,接下來我們一步步剖析源碼去了解。
查看JDK源碼,發現該方法在Object類中定義,是一個實例方法(Java中采用對象鎖):

public final void wait() throws InterruptedException {
        wait(0);
}

wait()方法調用了wait(0),該方法是一個native方法:

//The current thread must own this object's monitor.(調用該方法前,必須用于對象鎖)
public final native void wait(long timeout) throws InterruptedException;

查看OpenJDK源碼,openjdk\jdk\src\share\native\java\lang\Object.c

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

可以看到wait 對應的是 native 方法是JVM_MonitorWait, 繼續查看JVM_MonitorWait的實現:openjdk\hotspot\src\share\vm\prims\jvm.cpp

VM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
  JVMWrapper("JVM_MonitorWait");
  Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
  assert(obj->is_instance() || obj->is_array(), "JVM_MonitorWait must apply to an object");
  JavaThreadInObjectWaitState jtiows(thread, ms != 0);
  if (JvmtiExport::should_post_monitor_wait()) {
    JvmtiExport::post_monitor_wait((JavaThread *)THREAD, (oop)obj(), ms);
  }
  ObjectSynchronizer::wait(obj, ms, THREAD);

在該方法中調用了 ObjectSynchronizer::wait(obj, ms, THREAD)方法,具體實現如下:

void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
  //是否采用偏向鎖
  if (UseBiasedLocking) {
    BiasedLocking::revoke_and_rebias(obj, false, THREAD);
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
  }
  //如果wait time <0,拋出異常
  if (millis < 0) {
    TEVENT (wait - throw IAX) ;
    THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
  }
  //獲取ObjectMonitor,調用wait()方法
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
  DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
  monitor->wait(millis, true, THREAD);

  /* This dummy call is in place to get around dtrace bug 6254741.  Once
     that's fixed we can uncomment the following line and remove the call */
  // DTRACE_MONITOR_PROBE(waited, monitor, obj(), THREAD);
  dtrace_waited_probe(monitor, obj, THREAD);
}

發現最終是調用ObjectMonitor得wait()方法將線程加入阻塞隊列,ObjectMonitor類定義源碼位置:openjdk\hotspot\src\share\vm\runtime\objectMonitor.hpp,下面是ObjectMonitor類的部分定義(抽取了重要屬性):

  friend class ObjectSynchronizer;
  friend class ObjectWaiter;
  volatile markOop   _header;       // 對象頭
  void*     volatile _object;       // backward object pointer - strong root
  void *  volatile _owner;          // 該鎖的擁有著
  volatile jlong _previous_owner_tid; //  上一個擁護該鎖的線程ID
  ObjectWaiter * volatile _EntryList ;     // 等待鎖釋放的線程
  volatile intptr_t  _waiters;      // 等待線程的數量
  ObjectWaiter * volatile _WaitSet; // 等待鎖的線程列表,調用wait()方法,里面的線程需要等待notify()方法的調用

在ObjectMonitor中定義了_WaitSet屬性,是ObjectWaiter類型的一個指針,該類表示等待鎖的線程,具體的定義如下:

class ObjectWaiter : public StackObj {
 public:
  enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
  enum Sorted  { PREPEND, APPEND, SORTED } ;
  ObjectWaiter * volatile _next; //前一個ObjectWaiter
  ObjectWaiter * volatile _prev;//后一個ObjectWaiter
  Thread*       _thread; //等待的線程
  jlong         _notifier_tid;
  ParkEvent *   _event;
  volatile int  _notified ;
  volatile TStates TState ;
  Sorted        _Sorted ;           // List placement disposition
  bool          _active ;           // Contention monitoring is enabled
 public:
  ObjectWaiter(Thread* thread);

  void wait_reenter_begin(ObjectMonitor *mon);
  void wait_reenter_end(ObjectMonitor *mon);
};

通過查看ObjectWaiter類的定義,得知ObjectMonitor通過一個雙向鏈表來保存等待該鎖的線程。當調用Object.wait()方法時,在JVM中會調用ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS)方法,下面時方法的具體實現:

void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
  ...................
   // 創建ObjectWaiter,添加到_WaitSet隊列中
   ObjectWaiter node(Self);
   node.TState = ObjectWaiter::TS_WAIT ;
   Self->_ParkEvent->reset() ;
   OrderAccess::fence();          // ST into Event; membar ; LD interrupted-flag

 //WaitSetLock保護等待隊列。通常只鎖的擁有著才能訪問等待隊列
   Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;
 //加入等待隊列,等待隊列是循環雙鏈表
   AddWaiter (&node) ;
   //使用的是一個簡單的自旋鎖
   Thread::SpinRelease (&_WaitSetLock) ;
   .....................
}

調用wait()方法以后線程進入等待隊列,在其他線程中調用notify方法或者notifyAll()喚醒等待的隊列。在JVM中,通過調用void ObjectMonitor::notify(TRAPS) 方法喚醒等待的線程。由于篇幅問題具體代碼,請自行查看JDK源碼。

結束線程

線程的結束其實就是結束run()方法的調用,具體有三種方案。

方案一: 通過判斷變量的值結束線程,參考代碼如下:

public class ThreadExample extends Thread {
    private volatile boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            System.out.println(".....");
        }
        System.out.println("sub thread end....");
    }
    public static void main(String[] args) throws InterruptedException {
        ThreadExample example = new ThreadExample();
        example.start();
        Thread.sleep(1000 * 3);
        System.out.println("asking thread stop.....");
        example.flag = false;
        Thread.sleep(1000 * 3);
        System.out.println("main thread stop......");
    }
}

在該程序中,通過判斷一個boolean類型的變量來決定是否跳出while循環,結束run()方法的調用。

方案二: Thread.interrupt()

調用Thread實例的 interrupt() 方法,接下來該方法會調用

 // Just to set the interrupt flag(該方法只是設置一個中斷標志)
private native void interrupt0();

所以說, interrupt() 方法不會真正的中斷一個線程,這是實在一個中斷標志,在線程中判斷線程標志,結束run()方法的調用。參考代碼如下:

public class ThreadExample2 extends Thread {
    public static void main(String[] args) throws InterruptedException {
        ThreadExample2 test = new ThreadExample2();
        test.start();
        Thread.sleep(1000 * 3);
        test.interrupt();
        Thread.sleep(1000 * 3);
        System.out.println("main thread end .....");
    }
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println(".......");
        }
        System.out.println("sub thread end.....");
    }
}

方案三:Thread.interrupt()+變量的值判斷

如果線程已經調用了Thread.sleep(),wait()方法導致線程阻塞,線程不會去判斷變量的值中斷線程,在阻塞狀態調用 interrupt() 會拋出一個中斷信號,使得線程退出阻塞,同時清除中斷標志??梢越Y合:Thread.interrupt()+變量的值判斷實現線程的停止,參考代碼如下:

class ThreadExample3 extends Thread {
    volatile boolean stop = false;
    public static void main(String args[]) throws Exception {
        ThreadExample3 thread = new ThreadExample3();
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        /*
         * 如果線程阻塞,將不會檢查此變量,調用interrupt之后,線程就可以盡早的終結被阻 塞狀 態,能夠檢查這一變量。
         */
        thread.stop = true;
        /*
         * 這一方法實際上完成的是,在線程受到阻塞時拋出一個中斷信號,這樣線程就得以退 出阻 塞的狀態
         */
        thread.interrupt();
        Thread.sleep(3000);
        System.out.println("main thread end...");
        System.exit(0);
    }
    public void run() {
        while (!stop) {
            System.out.println("...........");
            try {
                Thread.sleep(1000 * 10);
            } catch (InterruptedException e) {
                // 接收到一個中斷異常(InterruptedException),從而提早地終結被阻塞狀態
                System.out.println("Thread interrupted...");
            }
        }
        if(Thread.currentThread().isInterrupted()) {
            System.out.println("-------有中斷標志---------");
        }else {
            System.out.println("-------無中斷標志---------");
        }
        System.out.println("Thread exiting under request...");
    }
}

Ok,今天的總結完畢,跑路。。。。。。。。。
參考:http://blog.csdn.net/lirenzuo/article/details/75911548

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 本文主要講了java中多線程的使用方法、線程同步、線程數據傳遞、線程狀態及相應的一些線程函數用法、概述等。 首先講...
    李欣陽閱讀 2,482評論 1 15
  • Java多線程學習 [-] 一擴展javalangThread類 二實現javalangRunnable接口 三T...
    影馳閱讀 2,981評論 1 18
  • 該文章轉自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍閱讀 7,378評論 3 87
  • 寫在前面的話: 這篇博客是我從這里“轉載”的,為什么轉載兩個字加“”呢?因為這絕不是簡單的復制粘貼,我花了五六個小...
    SmartSean閱讀 4,776評論 12 45
  • 1 不管是男孩子還是女孩子,都對自己朋友的另一半遠一點,這是最起碼的尊重,就算是真的沒有什么,但是在別人眼里呢???..
    666清水閱讀 286評論 0 0