多線程- 讓程序更高效的運行

Java Thread 的一些認識:html

  • Java是搶佔式線程,一個線程就是進程中單一的順序控制流,單個進程能夠擁有多個併發任務,其底層是切分CPU時間,多線程和多任務每每是使用多處理器系統的最合理方式
  • 進程能夠看做一個程序或者一個應用;線程是進程中執行的一個任務,多個線程能夠共享資源
  • 一個Java 應用從main 方法開始運行,main 運行在一個線程內,也被稱爲 「主線程」,Runnable也能夠理解爲Task (任務)
  • JVM啓動後,會建立一些守護線程來進行自身的常規管理(垃圾回收,終結處理),以及一個運行main函數的主線程
  • 隨着硬件水平的提升,多線程能使系統的運行效率獲得大幅度的提升,同時異步操做也增長複雜度和各類併發問題

 ■ 線程 VS 進程java

  • 在一個已有進程中建立一個新線程比建立一個新進程快的多
  • 終止一個線程比終止一個進程快的多
  • 同一個進程內線程間切換比進程間切換更快
  • 線程提供了不一樣的執行程序間通訊的效率,同一個進程中的線程共享同一進程內存和文件,無序調用內核就能夠互相通訊,而進程間通訊必須經過內核

 ■ 同步和異步程序員

  • 同步方法一旦開始,調用者必須等到方法調用返回以後,才能繼續後續行爲
  • 無前後順序,一旦開始,方法調用便當即返回,調用者就能夠繼續後續行爲,通常爲另外一個線程執行

 ■ 阻塞和非阻塞算法

  • 當一個線程佔用臨界區資源,其餘線程也想要使用該資源就必須等待,等待會致使線程的掛起,也就是阻塞(線程變成阻塞狀態)。
    此時若佔用資源的線程一直不肯意釋放資源,那麼其餘全部阻塞在該臨界區的線程都會被掛起,變成阻塞狀態,不能正常工做,直到佔用線程釋放資源
  • 非阻塞強調沒有一個線程能夠妨礙其餘線程執行,全部線程都會嘗試去作下一步工做

 ■ 臨界資源與臨界區編程

  • 通常指的是公共共享資源,便可以被多個線程共享使用。但同一時間只能由一個線程去訪問和操做臨界區的資源,一旦臨界區資源被一個線程佔用,其餘線程也想要使用該資源就必須等待,
    就比如好多人想上大號,但只有一個坑,一我的佔了坑,其餘人就得排隊等待嘍
  • 臨界區能夠認爲是一段代碼,線程會在該端代碼中訪問共享資源,所以臨界區的界定標準就是是否訪問共享(臨界)資源(有點相似造成閉包的概念);一次只容許有一個程序(進程/線程)在該臨界區中

 ■ 上下文切換數組

  • CPU經過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個任務。可是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時能夠從新加載這個任務的狀態。全部任務從保存到再加載的過程就是一次上下文切換
  • 多線程性能問題:因爲線程有建立和上下文切換的開銷,在多線程環境下,這種開銷對時間和資源的利用都是一個極大的負擔,極可能致使併發任務執行速度還不如串行快
  • 減小上下文切換: 無鎖併發編程、CAS算法、減小併發、使用最少線程、協程
      無鎖併發編程:避免使用鎖,好比數據分段執行(MapReduce)、儘量使用無狀態對象、避免競爭狀況等
      CAS算法:java.util.concurrent包中大量使用CAS算法,好比Atomic、AQS等
      減小併發:JAVA8中新引入的LongAdder、DoubleAdder等新類,將CAS算法替換成value分擔原則
      使用最少線程:避免建立沒必要要的線程,當任務不多但線程不少時,會致使大量線程爲等待狀態
      協程:在單線程裏實現多任務的調度,並在單線程裏維持多個任務間的切換安全

  • 補充:須要注意的是,Java的線程是映射到操做系統的原生線程上,所以若要阻塞或喚醒一個線程都須要操做系統的協助,這就意味着要從用戶態轉換到核心態,所以狀態轉換是很是耗費處理器時間的

 ■ 競合條件多線程

  • 競爭條件指多個線程併發訪問和操做同一數據且執行結果與線程訪問的特定順序有關
  • 競爭條件發生在當多個線程在讀寫數據時,其最終的的結果依賴於多個線程指令執行順序
  • 因爲競爭條件的指令順序操做的不肯定性甚至是錯誤的,可能會形成結果的混亂,例如臭名昭著的i++的原子性問題
  • 值得注意的是即便在單處理器環境下,也可能由於中斷的能夠在任何地方中止指令的執行的特性,致使相似併發狀況下的數據不一致性,解決方案同競爭條件一致:保證指令的執行順序

■ 併發級別閉包

     A. 阻塞(Blocking)併發

  • 線程如果阻塞的,那麼在其餘線程釋放資源以前,當前線程會被掛起,沒法繼續執行
  • 在Java中,若使用 synchronized 關鍵字或者重入鎖時,獲得的就是阻塞的線程
  • 不管是 synchronized 仍是重入鎖,都會試圖在執行後續代碼以前,競爭臨界區的鎖:
      若是競爭成功,當前線程會得到鎖並佔用資源,從而繼續日後執行
      若是競爭失敗,繼續掛起阻塞,等待下次資源被釋放後的再次競爭

 B.  無飢餓(Starvation-Free)

  • 若線程間區分優先級,那麼線程調度一般會優先知足高優先級的線程(非公平原則),就可能產生飢餓
  • 對於非公平的鎖來講,系統容許高優先級的線程插隊,這樣就可能致使低優先級線程產生飢餓 ,如 ReentrantLock 非公平構造 sync = new NonfairSync()
  • 對於公平的鎖來講,無論新到的線程優先級多高都須要乖乖排隊,全部線程都有機會執行,飢餓很難產生,ReentrantLock 公平構造 sync = boolean fair ? new FairSync() : new NonfairSync()
  • 當一個任務很是耗時致使某線程一直佔據關鍵資源不放,其餘線程難以獲取,此時其餘線程也能夠說是飢餓的

 

■ 線程狀態流程圖:

 

 Java 線程轉換狀態 (重要)

  • 新建(new):新建立一個線程對象:在JAVA中的表現就是Thread thread = new Thread();
  • 就緒(runnable):線程建立後,其餘線程調用該對象的start方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU時間分片使用權:在JAVA中的表現就是 thread.start()
  • 運行(running):就緒態線程獲取到CPU時間分片以後,就能夠執行任務:在JAVA中的表現就是thread.run(),但須要注意的是,此時線程不必定是當即執行,這跟系統調度有關
  • 阻塞(block):阻塞狀態是指線程由於某種緣由放棄CPU使用權,讓出CPU時間片,暫時中止運行(注意此時線程在內存中是存在的,並無被GC),直到線程進入就緒態,纔有機會再次獲取時間片轉成運行態。阻塞分三種狀況:
    •   常規阻塞:運行態線程在發出I/O請求、執行Thread.sleep方法、t.join方法時,JVM會將該線程設置爲阻塞狀態;當I/O處理完畢併發出響應、sleep方法超時、join等待線程終止或超時,線程從新轉入就緒態
    •   同步阻塞:運行態線程在獲取對象的同步鎖時,當該同步鎖已被其餘線程佔用,JVM會將該線程暫時放入同步隊列中,當其餘線程放棄同步鎖同時該線程競爭到該同步鎖時,該線程轉爲就緒態
    •   等待阻塞:運行態線程執行wait方法,JVM會將該線程放入等待隊列中,直到被其餘線程喚醒或其餘線程中斷或超時,再次獲取同步鎖標識,從新進入就緒態`
  • 死亡(dead):線程main方法、run方法執行完畢或出現異常,則該線程生命週期結束。處於死亡或終結狀態的線程不可再次被調度,不可被分配到時間片;已死亡線程不可復生,固然可使用線程池機制來提升線程的複用性,避免線程被直接殺死;此時其寄存器上下文和棧都將被釋放

 

 Java 線程枚舉狀態

/** 
  * JAVA對於線程狀態的枚舉類,使用jstack查看dump文件能夠看到相對應的線程狀態
  * 注意:狀態的轉換要以狀態圖爲參照標準,枚舉類只是用來統一記錄某種狀態以方便JAVA代碼編寫!
  * 對應JVM中對線程的監控的4種核心抽象狀態:
  *     運行(running),休眠(Sleeping),等待(Wait),監視(Monitor)
  */
public enum State {
    /**
      * Thread state for a thread which has not yet started.
      *     新建:線程建立但還不是就緒態(Thread尚未執行start方法)
      */
        NEW,
    /**
      * Thread state for a runnable thread.  A thread in the runnable
      * state is executing in the Java virtual machine but it may be waiting  
      * for other resources from the operating system such as processor.
      *     運行狀態:Java將就緒態和運行態統一設置爲RUNNABLE
      *     筆者認爲這可能與Thread執行start方法以後會當即執行run方法有關
      */
    RUNNABLE,
    /**
      * Thread state for a thread blocked waiting for a monitor lock.
      * A thread in the blocked state is waiting for a monitor lock to enter 
      * a synchronized block/method or reenter a synchronized block/method
      * after calling {@link Object#wait() Object.wait}.
      *     阻塞:線程正等待獲取監視鎖(同步鎖),調用wait方法就會阻塞當前線程
      *     只有獲取到鎖的線程才能進入或重入同步方法或同步代碼
      */
    BLOCKED,
    /**
      * Thread state for a waiting thread.
      * A thread is in the waiting state due to calling one of the following methods:
      * <ul>
      *   <li>{@link Object#wait() Object.wait} with no timeout</li>
      *   <li>{@link #join() Thread.join} with no timeout</li>
      *   <li>{@link LockSupport#park() LockSupport.park}</li>
      * </ul>
      *     調用以上方法會使線程進入等待狀態
      * <p>A thread in the waiting state is waiting for another thread to
      * perform a particular action.
      *     進入等待狀態的線程,須要等待其餘線程的喚醒才能繼續運行
      * For example, a thread that has called <tt>Object.wait()</tt>
      * on an object is waiting for another thread to call
      * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
      * that object. A thread that has called <tt>Thread.join()</tt>
      * is waiting for a specified thread to terminate.
      *     好比線程被wait方法執行等待,須要被notify或notifyAll喚醒
      *     再好比join方法會等待一個指定線程結束以後纔會繼續運行
      */
    WAITING,
    /**
      * Thread state for a waiting thread with a specified waiting time.
      * A thread is in the timed waiting state due to calling one of
      * the following methods with a specified positive waiting time:
      * <ul>
      *   <li>{@link #sleep Thread.sleep}</li>
      *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
      *   <li>{@link #join(long) Thread.join} with timeout</li>
      *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
      *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
      * </ul>
      *     調用以上方法會使線程進入等待狀態,但會超時返回
      */
    TIMED_WAITING,
    /**
      * Thread state for a terminated thread.
      * The thread has completed execution.
      *     終止:當線程任務完成以後,就終止了
      */
    TERMINATED;
    }

 

Thread 類定義

public class Thread implements Runnable {
  /* Make sure registerNatives is the first thing <clinit> does. 
    初始化時調用 Java 本地方法,實現了Runnable接口
  */ private static native void registerNatives(); static { registerNatives(); }

■ 構造器

/**
  * 默認構造器
  * 其中name規則爲 "Thread-" + nextThreadNum()
  */
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
/**
  * 建立一個指定Runnable的線程
  * 其中name規則爲 "Thread-" + nextThreadNum()
  */
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
/**
  * 建立一個指定所屬線程組和Runnable的線程
  * 其中name規則爲 "Thread-" + nextThreadNum()
  */
public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}
/**
  * 建立一個指定name線程
  */
public Thread(String name) {
    init(null, null, name, 0);
}
/**
  * 建立一個指定所屬線程組和name的線程
  */
public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}
/**
  * 建立一個指定Runnable和name的線程
  */
public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}
/**
  * Allocates a new {@code Thread} object so that it has {@code target}
  * as its run object, has the specified {@code name} as its name,
  * and belongs to the thread group referred to by {@code group}.
  *     建立一個新的Thread對象,同時知足如下條件:
  *         1.該線程擁有一個指定的Runnable對象用於方法執行
  *         2.該線程具備一個指定的名稱
  *         3.該線程屬於一個指定的線程組ThreadGroup
  * <p>If there is a security manager, its
  * {@link SecurityManager#checkAccess(ThreadGroup) checkAccess}
  * method is invoked with the ThreadGroup as its argument.
  *     若這裏有個安全管理器,則ThreadGroup將調用checkAccess方法進而觸發SecurityManager的checkAccess方法
  * <p>In addition, its {@code checkPermission} method is invoked with
  * the {@code RuntimePermission("enableContextClassLoaderOverride")}
  * permission when invoked directly or indirectly by the constructor of a subclass which
  * overrides the {@code getContextClassLoader} or {@code setContextClassLoader} methods.
  *     當enableContextClassLoaderOverride被開啓時,checkPermission將被重寫子類直接或間接地調用
  * <p>The priority of the newly created thread is set equal to the
  * priority of the thread creating it, that is, the currently running
  * thread. The method {@linkplain #setPriority setPriority} may be
  * used to change the priority to a new value.
  *     新建立的Thread的優先級等同於建立它的線程的優先級,調用setPriority會變動其優先級
  * <p>The newly created thread is initially marked as being a daemon
  * thread if and only if the thread creating it is currently marked
  * as a daemon thread. The method {@linkplain #setDaemon setDaemon}
  * may be used to change whether or not a thread is a daemon.
  *     當且僅當線程建立時被顯示地標記爲守護線程,新建立的線程纔會被初始化爲一個守護線程
  *     setDaemon方法能夠設置當前線程是否爲守護線程
  */
public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}
/**
  * Allocates a new {@code Thread} object so that it has {@code target} as its run object,
  * has the specified {@code name} as its name, and belongs to the thread group referred to
  * by {@code group}, and has the specified <i>stack size</i>.
  *     建立一個新的Thread對象,同時知足如下條件:
  *         1.該線程擁有一個指定的Runnable對象用於方法執行
  *         2.該線程具備一個指定的名稱
  *         3.該線程屬於一個指定的線程組ThreadGroup
  *         4.該線程擁有一個指定的棧容量
  * <p>This constructor is identical to {@link #Thread(ThreadGroup,Runnable,String)}
  * with the exception of the fact that it allows the thread stack size to be specified. 
  * The stack size is the approximate number of bytes of address space that the virtual machine
  * is to allocate for this thread's stack. <b>The effect of the {@code stackSize} parameter,
  * if any, is highly platform dependent.</b> 
  *     棧容量指的是JVM分配給該線程的棧的地址(內存)空間大小,這個參數的效果高度依賴於JVM運行平臺 
  * <p>On some platforms, specifying a higher value for the {@code stackSize} parameter may allow
  * a thread to achieve greater  recursion depth before throwing a {@link StackOverflowError}.
  * Similarly, specifying a lower value may allow a greater number of threads to exist
  * concurrently without throwing an {@link OutOfMemoryError} (or other internal error).
  * The details of the relationship between the value of the <tt>stackSize</tt> parameter
  * and the maximum recursion depth and concurrency level are platform-dependent.
  * <b>On some platforms, the value of the {@code stackSize} parameter
  * may have no effect whatsoever.</b>
  *     在一些平臺上,棧容量越高,(會在棧溢出以前)容許線程完成更深的遞歸(換句話說就是棧空間更深) 
  *     同理,若棧容量越小,(在拋出內存溢出以前)容許同時存在更多的線程數
  *     對於棧容量、最大遞歸深度和併發水平之間的關係依賴於平臺
  * <p>The virtual machine is free to treat the {@code stackSize} parameter as a suggestion. 
  * If the specified value is unreasonably low for the platform,the virtual machine may instead 
  * use some platform-specific minimum value; if the specified value is unreasonably  high, 
  * the virtual machine may instead use some platform-specific maximum. 
  * Likewise, the virtual machine is free to round the specified value up or down as it sees fit
  * (or to ignore it completely).
  *     JVM會將指定的棧容量做爲一個參考依據,但當小於平臺最小值時會直接使用最小值,最大值同理
  *     一樣,JVM會動態調整棧空間的大小以適應程序的運行或者甚至直接就忽視該值的設置
  * <p><i>Due to the platform-dependent nature of the behavior of this constructor, extreme care
  * should be exercised in its use.The thread stack size necessary to perform a given computation 
  * will likely vary from one JRE implementation to another. In light of this variation, 
  * careful tuning of the stack size parameter may be required,and the tuning may need to
  * be repeated for each JRE implementation on which an application is to run.</i>
  *     簡單總結一下就是:這個值嚴重依賴平臺,因此要謹慎使用,多作測試驗證
  * @param  group
  *         the thread group. If {@code null} and there is a security
  *         manager, the group is determined by {@linkplain
  *         SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}.
  *         If there is not a security manager or {@code
  *         SecurityManager.getThreadGroup()} returns {@code null}, the group
  *         is set to the current thread's thread group.
  *             當線程組爲null同時有個安全管理器,該線程組由SecurityManager.getThreadGroup()決定
  *             當沒有安全管理器或getThreadGroup爲空,該線程組即爲建立該線程的線程所屬的線程組
  * @param  target
  *         the object whose {@code run} method is invoked when this thread
  *         is started. If {@code null}, this thread's run method is invoked.
  *             若該值爲null,將直接調用該線程的run方法(等同於一個空方法)
  * @param  name
  *         the name of the new thread
  * @param  stackSize
  *         the desired stack size for the new thread, or zero to indicate
  *         that this parameter is to be ignored.
  *             當棧容量被設置爲0時,JVM就會忽略該值的設置
  * @throws  SecurityException
  *          if the current thread cannot create a thread in the specified thread group
  *             若是當前線程在一個指定的線程組中不能建立一個新的線程時將拋出 安全異常
  * @since 1.4
  */    
public Thread(ThreadGroup group, Runnable target, String name,long stackSize) {
    init(group, target, name, stackSize);
}

 

■ JVM棧異常分類

 根據棧異常的不一樣,主要有兩種分類:
 1) 棧溢出:若線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError異常

 2) 內存溢出: 若虛擬機棧能夠動態擴展(當前大部分的Java虛擬機均可動態擴展,只不過Java虛擬機規範中也容許固定長度的虛擬機棧),若是擴展時沒法申請到足夠的內存,就會拋出OutOfMemoryError異常

 

■ 重要變量

//線程名,用char來保存(String底層實現就是char)
private char        name[];
//線程優先級
private int         priority;
//不明覺厲
private Thread      threadQ;
//不明覺厲
private long        eetop;
/* Whether or not to single_step this thread. 不明覺厲*/
private boolean     single_step;
/* Whether or not the thread is a daemon thread. 是不是守護線程,默認非守護線程*/
private boolean     daemon = false;
/* JVM state. 是否一出生就領便當,默認false*/
private boolean     stillborn = false;
/* What will be run. Thread的run方法最終會調用target的run方法*/
private Runnable target;
/* The group of this thread. 當前線程的所屬線程組*/
private ThreadGroup group;
/* The context ClassLoader for this thread 當前線程的ClassLoader*/
private ClassLoader contextClassLoader;
/* The inherited AccessControlContext of this thread 當前線程繼承的AccessControlContext*/
private AccessControlContext inheritedAccessControlContext;
/* For autonumbering anonymous threads. 給匿名線程自動編號,並按編號起名字*/
private static int threadInitNumber;
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. 
 * 當前線程附屬的ThreadLocal,而ThreadLocalMap會被ThreadLocal維護(ThreadLocal會專門分析)
 */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
 * InheritableThreadLocal values pertaining to this thread. This map is
 * maintained by the InheritableThreadLocal class.
 * 主要做用:爲子線程提供從父線程那裏繼承的值
 * 在建立子線程時,子線程會接收全部可繼承的線程局部變量的初始值,以得到父線程所具備的值
 * 建立一個線程時若是保存了全部 InheritableThreadLocal 對象的值,那麼這些值也將自動傳遞給子線程
 * 若是一個子線程調用 InheritableThreadLocal 的 get() ,那麼它將與它的父線程看到同一個對象
 */
 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
/*
 * The requested stack size for this thread, or 0 if the creator did not specify a stack size. 
 * It is up to the VM to do whatever it likes with this number; some VMs will ignore it.
 * 棧容量:當設置爲0時,JVM會忽略該值;該值嚴重依賴於JVM平臺,有些VM甚至會直接忽視該值
 * 該值越大,線程棧空間變大,容許的併發線程數就越少;該值越小,線程棧空間變小,容許的併發線程數就越多
 */
private long stackSize;
/* JVM-private state that persists after native thread termination.*/
private long nativeParkEventPointer;
/* Thread ID. 每一個線程都有專屬ID,但名字可能重複*/
private long tid;
/* For generating thread ID 用於ID生成,每次+1*/
private static long threadSeqNumber;
/* 
 * Java thread status for tools,initialized to indicate thread 'not yet started'
 * 線程狀態 0僅表示已建立
 */
private volatile int threadStatus = 0;
/**
  * The argument supplied to the current call to java.util.concurrent.locks.LockSupport.park.
  * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
  * Accessed using java.util.concurrent.locks.LockSupport.getBlocker
  * 主要是提供給 java.util.concurrent.locks.LockSupport該類使用
  */
volatile Object parkBlocker;
/* The object in which this thread is blocked in an interruptible I/O operation, if any. 
 * The blocker's interrupt method should be invoked after setting this thread's interrupt status.
 * 中斷阻塞器:當線程發生IO中斷時,須要在線程被設置爲中斷狀態後調用該對象的interrupt方法
 */
private volatile Interruptible blocker;
//阻塞器鎖,主要用於處理阻塞狀況
private final Object blockerLock = new Object();
 /* The minimum priority that a thread can have. 最小優先級*/
public final static int MIN_PRIORITY = 1;
/* The default priority that is assigned to a thread. 默認優先級*/
public final static int NORM_PRIORITY = 5;
/* For generating thread ID 最大優先級*/
public final static int MAX_PRIORITY = 10;
/* 用於存儲堆棧信息 默認是個空的數組*/
private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
private static final RuntimePermission SUBCLASS_IMPLEMENTATION_PERMISSION =
            new RuntimePermission("enableContextClassLoaderOverride");
// null unless explicitly set 線程異常處理器,只對當前線程有效
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
// null unless explicitly set 默認線程異常處理器,對全部線程有效
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler; 

■ 本地方法

/* 
 * Make sure registerNatives is the first thing <clinit> does. 
 * 確保clinit最早調用該方法:全部該方法是類中的最靠前的一個靜態方法
 * clinit:在JVM第一次加載class文件時調用,用於靜態變量初始化語句和靜態塊的執行
 * 全部的類變量初始化語句和類型的靜態初始化語句都被Java編譯器收集到該方法中
 * 
 * registerNatives方法被native修飾,便是本地方法,將由C/C++去完成,並被編譯成了.dll,供JAVA調用
 * 其主要做用是將C/C++中的方法映射到Java中的native方法,實現方法命名的解耦
 */
private static native void registerNatives();
static {
    registerNatives();
}
/** 主動讓出CPU資源,當時可能又當即搶到資源 **/
public static native void yield();
/** 休眠一段時間,讓出資源可是並不會釋放對象鎖 **/
public static native void sleep(long millis) throws InterruptedException;
/** 檢查 線程是否存活 **/
public final native boolean isAlive();
/** 檢查線程是否中斷 isInterrupted() 內部使用 **/
private native boolean isInterrupted(boolean ClearInterrupted);
/** 返回當前執行線程 **/
public static native Thread currentThread();
public static native boolean holdsLock(Object obj);
private native void start0();
private native void setPriority0(int newPriority);
private native void stop0(Object o);
private native void suspend0();
private native void resume0();
private native void interrupt0();
private native void setNativeName(String name);

■ 線程初始化

/**
  * Initializes a Thread.
  *     初始化一個線程
  * @param g the Thread group
  * @param target the object whose run() method gets called
  * @param name the name of the new Thread
  * @param stackSize the desired stack size for the new thread, or
  *        zero to indicate that this parameter is to be ignored.
  */
private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    //返回當前線程,即建立該hread的線程 currentThread是個本地方法
    Thread parent = currentThread();
    //安全管理器根據Java安全策略文件決定將哪組權限授予類
    //若是想讓應用使用安全管理器和安全策略,可在啓動JVM時設定-Djava.security.manager選項
    //還能夠同時指定安全策略文件
    //若是在應用中啓用了Java安全管理器,卻沒有指定安全策略文件,那麼Java安全管理器將使用默認的安全策略
    //它們是由位於目錄$JAVA_HOME/jre/lib/security中的java.policy定義的
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        /* Determine if it's an applet or not */
        /* If there is a security manager, ask the security manager what to do. */
        if (security != null) {
            g = security.getThreadGroup();
        }
        /* If the security doesn't have a strong opinion of the matter use the parent thread group. */
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
    /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */
    //判斷當前運行線程是否有變動其線程組的權限
    g.checkAccess();
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }
    //新建線程數量計數+1 或者說未就緒線程數+1==> nUnstartedThreads++;
    g.addUnstarted();
    this.group = g;
    this.daemon = parent.isDaemon();//若當前運行線程是守護線程,新建線程也是守護線程
    this.priority = parent.getPriority();//默認使用當前運行線程的優先級
    this.name = name.toCharArray();
    //設置contextClassLoader
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext = AccessController.getContext();
    this.target = target;
    setPriority(priority);//如有指定的優先級,使用指定的優先級
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;
    /* Set thread ID 線程安全,序列號每次同步+1*/
    tid = nextThreadID();
}
private static synchronized long nextThreadID() { return ++threadSeqNumber; }

■ start 方法

/**
  * Causes this thread to begin execution; the Java Virtual Machine
  * calls the <code>run</code> method of this thread.
  *     線程進入就緒態,隨後JVM將會調用這個線程run方法
  *     當獲取到CPU時間片時,會當即執行run方法,此時線程會直接變成運行態
  * <p>
  * The result is that two threads are running concurrently: the
  * current thread (which returns from the call to the
  * <code>start</code> method) and the other thread (which executes its
  * <code>run</code> method).
  * <p>
  * It is never legal to start a thread more than once.
  * In particular, a thread may not be restarted once it has completed execution.
  *     一個線程只能被start一次,特別是線程不會在執行完畢後從新start
  *     當線程已經start了,再次執行會拋出IllegalThreadStateException異常
  * @exception  IllegalThreadStateException  if the thread was already started.
  * @see        #run()
  * @see        #stop()
  */
public synchronized void start() {
    /**
      * This method is not invoked for the main method thread or "system"
      * group threads created/set up by the VM. Any new functionality added
      * to this method in the future may have to also be added to the VM.
      * 該方法不會被主線程或系統線程組調用,若將來有新增功能,也會被添加到VM中
      * A zero status value corresponds to state "NEW".
      * 0對應"已建立"狀態 -> 用常量或枚舉標識多好
      */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    //通知所屬線程組該線程已是就緒狀態,於是能夠被添加到該線程組中
    //同時線程組的未就緒線程數須要-1,對應init中的+1
    group.add(this);
    boolean started = false;
    try {
        //調用本地方法,將內存中的線程狀態變動爲就緒態
        //同時JVM會當即調用run方法,獲取到CPU以後,線程變成運行態並當即執行run方法
        start0();
        started = true;//標記爲已開啓
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);//若是變動失敗,要回滾線程和線程組狀態
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
                 it will be passed up the call stack */
            //若是start0出錯,會被調用棧直接經過
        }
    }
}
-------------
//start以後會當即調用run方法
Thread t = new Thread( new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
},"roman");
t.start();  //roman

 ■ run 方法

 /**
   * If this thread was constructed using a separate <code>Runnable</code> run object,
   * then that <code>Runnable</code> object's <code>run</code> method is called;
   * otherwise, this method does nothing and returns.
   * Subclasses of <code>Thread</code> should override this method.
   *    若Thread初始化時有指定Runnable就執行其的run方法,不然doNothing
   *    該方法必須被子類實現
   */
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

 ■ isAlive 方法

/**
  * Tests if this thread is alive. A thread is alive if it has
  * been started and has not yet died.
  *     測試線程是否處於活動狀態
  *     活動狀態:線程處於正在運行或者準備開始運行狀態
  * @return  <code>true</code> if this thread is alive;
  *          <code>false</code> otherwise.
  */
public final native boolean isAlive();

✺ 線程運行 : 模擬電梯運行類

public class LiftOffTask implements Runnable {  //Runnable 可看做任務(Task)
    private int countDown = 10;   //電梯階層

    public LiftOffTask(){
    }

    // syn countDown
    private synchronized int getCountDown(){
        --countDown;  // ++ -- 是非線程安全
        return countDown;
    }

    // getStatus
    public String status(){
        return Thread.currentThread().toString()+  "("+
                (countDown > 0? countDown: "Liftoff!") + "),";
    }

    @Override
    public void run(){
        while (getCountDown() >0){
            System.out.println(status());
            Thread.yield();
        }
    }

    public static void main(String[] args){
        Thread thread = new Thread(new LiftOffTask());
        thread.start();  // 調用 run()
        System.out.println("================ Waiting for LiftOff... ===========================");
    }

}

 ■ sleep 方法

/**
 * Causes the currently executing thread to sleep (temporarily cease execution)
 * for the specified number of milliseconds plus the specified number of nanoseconds, 
 * subject to the precision and accuracy of system timers and schedulers.
 * The thread does not lose ownership of any monitors.
 *      使線程睡眠一段毫秒時間,但線程並不會丟失已有的任何監視器
 */
public static void sleep(long millis, int nanos) throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException("nanosecond timeout value out of range");
    }
    //換算用
    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }
    sleep(millis);
}
/** 咱們通常會直接調用native方法,這或許是咱們主動使用的最屢次的native方法了 **/
public static native void sleep(long millis) throws InterruptedException;

 ■ 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.
  *  暗示線程調度器當前線程將釋放本身當前佔用的CPU資源
  *  線程調度器會自由選擇是否忽視此暗示
  * <p> Yield is a heuristic attempt to improve relative progression
  * between threads that would otherwise over-utilise a CPU. Its use
  * should be combined with detailed profiling and benchmarking to
  * ensure that it actually has the desired effect.
  *  該方法會放棄當前的CPU資源,將它讓給其餘的任務去佔用CPU執行時間
  *  但放棄的時間不肯定,可能剛剛放棄又得到CPU時間片
  * <p> It is rarely appropriate to use this method. It may be useful
  * for debugging or testing purposes, where it may help to reproduce
  * bugs due to race conditions. It may also be useful when designing
  * concurrency control constructs such as the ones in the
  * {@link java.util.concurrent.locks} package.
  *  該方法的適合使用場景比較少,主要用於Debug,好比Lock包設計
  */
public static native void yield();

 ✺ 線程讓步實例

public class ThreadYieldTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                long begin = System.currentTimeMillis();
                int count = 0;
                for (int i=1; i<1000000; i++) {
//                    Thread.yield();
                    count += i;
                }
                long end = System.currentTimeMillis();
                System.out.println("耗時:" + (end - begin) + "毫秒!");
            }
        }, "yieldTask");
        t1.start();
    }
}

 

 ■ 中斷機制 

    1. Java 終止線程主要有三個手段

  • 自動終止: 使用退出標識,使線程正常退出,即當run()方法完成以後線程自動終止
  • 強行終止: 使用stop()方法強行終止,但該方法已被棄用,使用它們可能產生不可預料的後果:該線程會當即中止,並拋出特殊的ThreadDeath()異常,若此時任務仍未執行完畢,可能產生髒數據
  • 手動終止: 使用interrupt()方法中斷線程,並在獲取到線程中斷時結束(退出)任務,固然 interrupt 並不是真正中斷線程,可由程序員自行處理

 

   2.  中斷機制

  因爲Java中沒法當即中止一個線程,而中止操做很重要,所以Java提供了一種用於中止線程的機制,即中斷機制:

  • 中斷狀態:在Java中每一個線程會維護一個Boolean中斷狀態位,用來代表當前線程是否被中斷,默認非中斷爲 false
  • 中斷方法:中斷僅僅只是一種協做方式(能夠直接理解爲開關標誌),JDK僅提供設置中斷狀態和判斷是否中斷的方法,如 interrupted() 和i isInterrupted()
  • 中斷過程:因爲JDK只負責檢測和更新中斷狀態,所以中斷過程必須由程序猿本身實現,包括中斷捕獲、中斷處理,所以如何優雅的處理中斷變得尤其重要

  

   3.  中斷方法

  • Thread.currentThread().isInterrupted(): 判斷是否中斷,對象方法,不會清除中斷狀態
  • Thread.interrupted(): 判斷是否中斷,靜態方法,會清除中斷狀態(設置爲false)
  • Thread.currentThread().interrupt(): 中斷操做,對象方法,會將線程的中斷狀態設置爲true,僅此而已(不會真正中斷線程),捕獲和處理中斷由程序猿自行實現

    中斷後:線程中斷後的結果是死亡、等待新的任務或是繼續運行至下一步,取決於程序自己
    /**
     * 中斷一個線程(實質是設置中斷標誌位,標記中斷狀態)
     *   - 線程只能被本身中斷,不然拋出SecurityException異常
     *   - 特殊中斷處理以下:
     *     1.若中斷線程被以下方法阻塞,會拋出InterruptedException同時清除中斷狀態:
     *     Object.wait()、Thread.join() or Thread.sleep()
     *
     *     2.若線程在InterruptibleChannel上發生IO阻塞,該通道要被關閉並將設置中斷狀態同時拋出ClosedByInterruptException異常
     *
     *     3.若線程被NIO多路複用器Selector阻塞,會設置中斷狀態且從select方法中當即返回一個非0值(當wakeup方法正好被調用時)
     *
     *   - 非上述狀況都會將線程狀態設置爲中斷
     *   - 中斷一個非活線程不會有啥影響
     */
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();
        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                // Just to set the interrupt flag
                // 調用interrupt方法僅僅是在當前線程中打了一箇中止的標記,並非真的中止線程!
                interrupt0();           
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

  使用 isInterrupt() 方法能夠測試一下: 

/**
  * Tests whether this thread has been interrupted.  
  * The <i>interruptedstatus< /i> of the thread is unaffected by this method.
  *     測試線程Thread對象是否已是中斷狀態,但不清除狀態標誌
  * @return  <code>true</code> if this thread has been interrupted;
  *          <code>false</code> otherwise.
  * @see     #interrupted()
  * @revised 6.0
  */
public boolean isInterrupted() {
    //會調用本地isInterrupted方法,同時不清除狀態標誌
    return isInterrupted(false);
}
/**
  * @param ClearInterrupted 是否清除狀態標註,false不清除,true清除
  */
private native boolean isInterrupted(boolean ClearInterrupted);
-------------
Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0 ;i < 10000;i++){}
        System.out.println(Thread.currentThread().getName());
    }
},"kira");
t2.start();
t2.interrupt();
System.out.println("是否中止 1 ?= " + t2.isInterrupted());//是否中止 1 ?=true
// 正常輸出了10000次 線程名

  設置中斷監聽:

Thread thread = new Thread(() -> {
    //循環監聽中斷狀態
    while(!Thread.currentThread().isInterrupted()) {
        //正常執行任務
    }
    //處理中斷
}).start();

 

    4.  中斷狀況

  1. 中斷非阻塞線程: volatile共享變量或使用interrupt(),前者須要本身實現,後者是JDK提供的
  2. 中斷阻塞線程: 當處於阻塞狀態的線程調用interrupt()時會拋出中斷異常,而且會清除線程中斷標誌(設置爲false);因爲中斷標誌被清除,若想繼續中斷,需在捕獲中斷異常後需從新調用interrupt()重置中斷標誌位(true)
  3. 不可中斷線程: synchronized 和 aquire()不可被中斷,但AQS提供了acquireInterruptibly()方法相應中斷

  • 中斷阻塞線程測試用例 - 有可能拋出 InterruptedException
Thread thread = new Thread(() -> {
    while(!Thread.currentThread().isInterrupted()) {
        System.out.println(Thread.currentThread().getName() + " while run ");
        try {
            System.out.println(Thread.currentThread().getName() + " sleep begin");
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName() + " sleep end");
        } catch (InterruptedException e) {
            //sleep方法會清空中斷標誌,若不從新中斷,線程會繼續執行
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
    if (Thread.currentThread().isInterrupted()) {
        System.out.println(Thread.currentThread().getName() + "is interrupted");
    }
});
thread.start();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
thread.interrupt();

 

 ■ Daemon

  • 分類:在JAVA中分紅兩種線程:用戶線程守護線程
  • 特性:當進程中不存在非守護線程時,則所有的守護線程會自動化銷燬
  • 應用: JVM在啓動後會生成一系列守護線程,最有名的當屬GC(垃圾回收器)
 1 Thread t2 = new Thread(new Runnable() {
 2     @Override
 3     public void run() {
 4         System.out.println("守護線程運行了");
 5         for (int i = 0; i < 500000;i++){
 6             System.out.println("守護線程計數:" + i);
 7         }
 8     }
 9 }, "kira");
10 t2.setDaemon(true);
11 t2.start();
12 Thread.sleep(500);
13 -------------
14 //輸出:
15 ......
16 守護線程計數:113755
17 守護線程計數:113756
18 守護線程計數:113757
19 守護線程計數:113758
20 //結束打印:會發現守護線程並無打印500000次,由於主線程已經結束運行了

 

線程間的通訊(重要)

線程與線程之間不是獨立的個體,彼此之間能夠互相通訊和協做:
    Java提供多種線程間通訊方案:輪詢機制、等待/通知機制、join()、ThreadLocal、Synchronized、Volatile等

     Volitile: Java內存模型 &Volatile

     Synchronized: JAVA 鎖之 Synchronied

     ThreadLocal: ThreadLocal 線程本地變量及源碼分析

 

   - sleep + while(true) 輪詢

public class SleepWhileThread {
    public static void main(String[] args) {
        final List<Integer> list = new ArrayList<Integer>();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run(){
                try {
                    for (int i=0; i<6; i++){
                        list.add(i);
                        System.out.println("添加了" + (i+1) + "個元素");
                    }
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "listAdd");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true) {
                        if(list.size() == 3) {
                            System.out.println("已添加3個元素, sleepWhile線程須要退出");
                            throw new InterruptedException();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();  //這裏只是打印堆棧信息,不是真的中止執行
                }
            }
        }, "sleepWhile");
        t1.start();
        t2.start();
    }
}
  • 難以保證及時性:睡眠時,基本不消耗資源,但睡眠時間過長(輪詢間隔時間較大),就不能及時發現條件變動
  • 難以下降開銷:減小睡眠時間(輪訓間隔時間較小),能更迅速發現變化,但會消耗更多資源,形成無故浪費
  • Java引入等待/通知 (wait/notify) 機制來減小CPU的資源浪費,同時還能夠時間在多個線程間的通訊

 

   - wait 和 notify 機制

    ■ Object.wait()

  • wait方法使當前線程進行等待,該方法是Object類的方法,用來將當前線程放入"等待隊列"中,並在wait所在的代碼處中止執行,直到收到通知被喚醒或被中斷或超時
  • 調用wait方法以前,線程必須得到該對象的對象級別鎖,即只能在同步方法或同步塊中調用wait方法
  • 在執行wait方法後,當前線程釋放鎖,在從wait方法返回前,線程與其餘線程競爭從新得到鎖
  • 若是調用wait方法時沒有持有適當的鎖,則拋出運行期異常類IllegalMonitorStateException

 

    ■ Object.notify()

  • notify方法使線程被喚醒,該方法是Object類的方法,用來將當前線程從"等待隊列中"移出到"同步隊列中",線程狀態從新變成阻塞狀態,notify方法所在同步塊釋放鎖後,從wait方法返回繼續執行
  • 調用notify方法以前,線程必須得到該對象的對象級別鎖,即只能在同步方法或同步塊中調用notify方法
  • 該方法用來通知那麼可能等待該對象的對象鎖的其餘線程,若是有多個線程等待,則由線程規劃器從等待隊列中隨機選擇一個WAITING狀態線程,對其發出通知轉入同步隊列並使它等待獲取該對象的對象鎖
  • 在執行notify方法以後,當前線程不會立刻釋放對象鎖,等待線程也並不能立刻獲取該對象鎖,須要等到執行notify方法的線程將程序執行完,即退出同步代碼塊以後當前線程才能釋放鎖,而等待線程才能夠有機會獲取該對象鎖
  • 若是調用notify方法時沒有持有適當的鎖,則拋出運行期異常類IllegalMonitorStateException

 

    ■ wait/notify 機制

  wait()使線程中止運行,notify()使中止的線程繼續運行
        1). 使用wait()、notify()、notifyAll()須要先對調用對象加鎖,即只能在同步方法或同步塊中調用這些方法
        2). 調用wait()方法後,線程狀態由RUNNING變成WAITING,並將當前線程放入對象的等待隊列中
        3). 調用notify()或notifyAll()方法以後,等待線程不會從wait()返回,須要notify()方法所在同步塊代碼執行完畢而釋放鎖以後,等待線程才能夠獲取到該對象鎖並從wait()返回
        4). notify()方法將隨機選擇一個等待線程從等待隊列中移到同步隊列中;notifyAll()方法會將等待隊列中的全部等待線線程所有移到同步隊列中,被移動線程狀態由WAITING變成BLOCKED

// wait/notify 實現線程調度
public
class NumberPrint implements Runnable { private int number; public byte[] res; public static int count = 5; public NumberPrint(int number, byte[] a ) { this.number = number; this.res = a; } @Override public void run() { synchronized (res) { while (count-- > 0){ try { res.notify(); System.out.println(" " + number); res.wait(); System.out.println("----------線程"+Thread.currentThread().getName() + "得到鎖,wait()後的代碼繼續運行:"+ number); System.out.println("count:" + count); //count 第一次循環爲3,由於兩個線程都執行了 count-- } catch (InterruptedException e){ e.printStackTrace(); } } return; } //syn end } public static void main(String[] args) { final byte[] a = {0}; new Thread(new NumberPrint(1,a),"1").start(); new Thread(new NumberPrint(2,a),"2").start(); } }

 

   - join() : 等待線程對象銷燬,可使得一個線程在另外一個線程結束後再執行,底層使用wait() 實現

/**
  * Waits at most {@code millis} milliseconds for this thread to
  * die. A timeout of {@code 0} means to wait forever.
  *     後續線程須要等待當前線程至多運行millis毫秒(超過millis當前線程會自動死亡,結束等待)
  *     若millis表示0,表示後續線程須要永遠等待(直到當前線程運行完畢)
  * <p> This implementation uses a loop of {@code this.wait} calls conditioned on 
  * {@code this.isAlive}. As a thread terminates the {@code this.notifyAll} method is invoked. 
  * It is recommended that applications not use {@code wait}, {@code notify}, or
  * {@code notifyAll} on {@code Thread} instances.
  *     該方法的原理是循環調用wait方法阻塞後續線程直到當前線程已經不是存活狀態了
  * @param  millis
  *         the time to wait in milliseconds
  * @throws  IllegalArgumentException
  *          if the value of {@code millis} is negative
  * @throws  InterruptedException
  *          if any thread has interrupted the current thread. The
  *          <i>interrupted status</i> of the current thread is
  *          cleared when this exception is thrown.
  */
//注意 join方法被synchronized修改,便是個同步方法,也是此處獲取到同步鎖,爲wait作好前提準備
//同時lock指的就是調用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");
    }
    //當millis爲0時,說明後續線程須要被無限循環等待,直到當前線程結束運行
    if (millis == 0) {
        while (isAlive()) {
            wait(0);//wait的超時時間爲0
        }
    } else {
        //當millis>0時,在millis毫秒內後續線程須要循環等待,直到超時當前線程自動死亡
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);//wait的超時時間爲delay
            now = System.currentTimeMillis() - base;
        }
    }
}
/**
  * Thread類還提供一個等待時間爲0的join方法
  * 用於將後續線程無限循環等待,直到當前線程結束運行
  */
public final void join() throws InterruptedException {
    join(0);
}

    join 使用

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"線程值爲:sally" + i);                    }
    }
},"sally");
Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0;i<2;i++){
            System.out.println(Thread.currentThread().getName()+"線程值爲:kira" + i);
        }
    }
},"kira");
t1.start();
t1.join();//讓t2線程和後續線程無限等待直到sally線程執行完畢
t2.start();
-------------
//輸出:
......
sally線程值爲:sally97
sally線程值爲:sally98
sally線程值爲:sally99
kira線程值爲:kira0 //能夠發現直到sally線程執行完畢,kira線程纔開始執行
kira線程值爲:kira1

    ■ join  VS  sleep

  • join(long)方法 在內部使用 wait(long)方法 進行等待,所以 join(long)方法 可以釋放鎖
  • Thread.sleep(long)方法 卻不會釋放鎖

    ■ join  VS  synchronized

  • join()方法 在內部使用 wait()方法 進行等待
  • `synchronized 使用的是對象監視器原理做爲同步

 

-------------------------------------------------------------------------------------------------------------------------------

PS 致謝:

***** 各位觀衆,因爲對線程的調度機制還理解比較淺,因此本文會持續迭代更新

***** 特別感謝個人好友kira 對本人的平常指導和源碼分析提供,一萬個Thank you! Kira *******

 

更新版本:

2018/2/8 : 添加線程狀態、線程中斷等論述

2018/4/14:  修改了線程中斷的一些說明;join() 說明

2018/9/27:  修改了一些版式,添加了 join() 的例子

相關文章
相關標籤/搜索