Java Thread 的一些認識:html
■ 線程 VS 進程java
■ 同步和異步程序員
■ 阻塞和非阻塞算法
■ 臨界資源與臨界區編程
■ 上下文切換數組
減小上下文切換: 無鎖併發編程、CAS算法、減小併發、使用最少線程、協程
無鎖併發編程:避免使用鎖,好比數據分段執行(MapReduce)、儘量使用無狀態對象、避免競爭狀況等
CAS算法:java.util.concurrent包中大量使用CAS算法,好比Atomic、AQS等
減小併發:JAVA8中新引入的LongAdder、DoubleAdder等新類,將CAS算法替換成value分擔原則
使用最少線程:避免建立沒必要要的線程,當任務不多但線程不少時,會致使大量線程爲等待狀態
協程:在單線程裏實現多任務的調度,並在單線程裏維持多個任務間的切換安全
■ 競合條件多線程
■ 併發級別閉包
A. 阻塞(Blocking)併發
不管是 synchronized 仍是重入鎖,都會試圖在執行後續代碼以前,競爭臨界區的鎖:
若是競爭成功,當前線程會得到鎖並佔用資源,從而繼續日後執行
若是競爭失敗,繼續掛起阻塞,等待下次資源被釋放後的再次競爭
B. 無飢餓(Starvation-Free)
■ 線程狀態流程圖:
Java 線程轉換狀態 (重要)
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 終止線程主要有三個手段
2. 中斷機制
因爲Java中沒法當即中止一個線程,而中止操做很重要,所以Java提供了一種用於中止線程的機制,即中斷機制:
3. 中斷方法
/** * 中斷一個線程(實質是設置中斷標誌位,標記中斷狀態) * - 線程只能被本身中斷,不然拋出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()方法相應中斷
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
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(); } }
- wait 和 notify 機制
■ Object.wait()
■ Object.notify()
■ 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 VS synchronized
-------------------------------------------------------------------------------------------------------------------------------
PS 致謝:
***** 各位觀衆,因爲對線程的調度機制還理解比較淺,因此本文會持續迭代更新
***** 特別感謝個人好友kira 對本人的平常指導和源碼分析提供,一萬個Thank you! Kira *******
更新版本:
2018/2/8 : 添加線程狀態、線程中斷等論述
2018/4/14: 修改了線程中斷的一些說明;join() 說明
2018/9/27: 修改了一些版式,添加了 join() 的例子