多執行緒入門(一)
-
1、程序與執行緒
定義:
程序:是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統構成的基礎。每個正在系統中執行的程式都是一個程序。
執行緒:執行緒是程序中的一個實體,是被系統獨立排程和分派的基本單位,執行緒自己不擁有系統資源,只擁有一點兒在執行中必不可少的資源,但它可與同屬一個程序的其它執行緒共享程序所擁有的全部資源。
區別:
1、程序是所有執行緒的集合,每一個執行緒是程序中的一條執行路徑;
2、程序是資源分配的最小單位,執行緒是程式執行的最小單位;
3、程序有自己的獨立地址空間,每啟動一個程序,系統就會為它分配地址空間,建立資料表來維護程式碼段、堆疊段和資料段,這種操作非常昂貴。而執行緒是共享程序中的資料的,使用相同的地址空間,因此CPU切換一個執行緒的花費遠比程序要小很多,同時建立一個執行緒的開銷也比程序要小很多;
4、執行緒之間的通訊更方便,同一程序下的執行緒共享全域性變數、靜態變數等資料,而程序之間的通訊需要以通訊的方式(IPC)進行。不過如何處理好同步與互斥是編寫多執行緒程式的難點。
-
2、使用場景及實現方式
使用場景:
1、後臺任務,例如:定時向大量(100w以上)的使用者傳送郵件、簡訊,迅雷多執行緒下載。
2、非同步處理,例如:發微博、記錄日誌等;
3、分散式計算。
實現方式:
1、繼承Thread類建立執行緒
Thread類本質上是實現了Runnable介面的一個例項,代表一個執行緒的例項。啟動執行緒的唯一方法就是通過Thread類的start()例項方法。start()方法是一個native方法,它將啟動一個新執行緒,並執行run()方法。
程式碼:
class CreateThread extends Thread { // run方法中編寫 多執行緒需要執行的程式碼 public void run() { for (int i = 0; i< 10; i++) { System.out.println("i:" + i); } } } public class ThreadDemo { public static void main(String[] args) { System.out.println("-----多執行緒建立開始-----"); // 1.建立一個執行緒 CreateThread createThread = new CreateThread(); // 2.開始執行執行緒 注意 開啟執行緒不是呼叫run方法,而是start方法 System.out.println("-----多執行緒建立啟動-----"); createThread.start(); System.out.println("-----多執行緒建立結束-----"); } }
2、實現Runnable介面建立執行緒
如果自己的類已經extends另一個類,就無法直接extends Thread(注:Java語言的特點“單根繼承”),此時,可以實現一個Runnable介面。
程式碼:
//功能描述:(建立多執行緒例子-Thread類 重寫run方法) class CreateRunnable implements Runnable { @Override publicvoid run() { for (inti = 0; i< 10; i++) { System.out.println("i:" + i); } } } //功能描述:(建立多執行緒例子-Thread類 重寫run方法) public class ThreadDemo2 { public static void main(String[] args) { System.out.println("-----多執行緒建立開始-----"); // 1.建立一個執行緒 CreateRunnable createThread = new CreateRunnable(); // 2.開始執行執行緒 注意 開啟執行緒不是呼叫run方法,而是start方法 System.out.println("-----多執行緒建立啟動-----"); Thread thread = new Thread(createThread); thread.start(); System.out.println("-----多執行緒建立結束-----"); } }
3、使用匿名內部類方式
程式碼:
public class InnerClassThread { public static void main(String[] args) { System.out.println("-----多執行緒建立開始-----"); Thread thread = new Thread(new Runnable() { public void run() { for (int i = 0; i< 10; i++) { System.out.println("i:" + i); } } }); thread.start(); System.out.println("-----多執行緒建立結束-----"); } }
-
3、執行緒的執行狀態
狀態分類:
Java中的執行緒大致存在六種狀態,有的說是五種(除去了“超時等待”)。
1、初始(NEW):新建立了一個執行緒物件,但還沒有呼叫start()方法。
2、執行(RUNNABLE):Java執行緒中將就緒(ready)和執行中(running)兩種狀態籠統的稱為“執行”。
3、阻塞(BLOCKED):表示執行緒阻塞於鎖。
4、等待(WAITING):進入該狀態的執行緒需要等待其他執行緒做出一些特定動作(通知或中斷)。
5、超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間後自行返回。處於這種狀態的執行緒不會被分配CPU執行時間,不過無須無限期等待被其他執行緒顯示地喚醒,在達到一定時間後它們會自動喚醒
6、終止(TERMINATED):表示該執行緒已經執行完畢。
狀態切換圖:
-
4、執行緒的API方法
Thread類中的屬性:
private volatile String name;//執行緒的名稱 private int priority;//執行緒的優先順序,最小1、預設5、最大10 private boolean daemon = false;//是否是守護執行緒 private Runnable target; //該執行緒要執行的任務 private ThreadGroup group; //該執行緒歸屬的執行緒組 private ClassLoader contextClassLoader; //載入器,預設為AppClassLoader private static int threadInitNumber; //數字標識匿名執行緒 ThreadLocal.ThreadLocalMap threadLocals = null; //執行緒私有變數 private long stackSize; //分配給執行緒的棧空間大小,預設0(不限制) private long tid; //執行緒id private static long threadSeqNumber; //執行緒序列 private volatile int threadStatus = 0; //執行緒執行狀態,預設是0(NEW) volatile Object parkBlocker; //java.util.concurrent.locks.LockSupport.park使用 private volatile Interruptible blocker; //列舉欄位,interrupt方法使用
Thread的構造方法:
public Thread(); public Thread(Runnable target); public Thread(Runnable target,String name); public Thread(String name); public Thread(ThreadGroup group, Runnable target); public Thread(ThreadGroup group, Runnable target, String name); /*分配一個新的 Thread物件,使其具有 target作為其執行物件,具有指定的 name作為其名稱,屬於 group引用的執行緒組。*/ public Thread(ThreadGroup group, Runnable target, String name, long stackSize) //ThreadGroup如果不指定,則為當前執行緒(即啟動該執行緒的執行緒)的ThreadGroup;Runnable就是該執行緒將要執行的類, //分兩種情況,一種是繼承了Thread類,一種是實現了Runnable介面;name就是執行緒的名稱,如果不指定則為"Thread-索引號(索引從0開始)";stackSize是JVM分配給執行緒的棧空間大小,預設0(不限制)
Thread中的關於自身的API:
//返回對當前正在執行的執行緒物件的引用 public static native Thread currentThread() //測試當前執行緒是否中斷 public static boolean interrupted() //使當前正在執行的執行緒以指定的毫秒數暫停(暫時停止執行) public static native void sleep(long millis) throws InterruptedException; //獲取該執行緒狀態,返回值為列舉,包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED public State getState() //判斷該執行緒是否存活,標準是執行緒已啟動但還沒有執行完畢 public final native boolean isAlive() //獲取當前jvm中該執行緒的唯一標示 public long getId() //判斷該執行緒是否是守護執行緒 public final boolean isDaemon() //設定執行緒的名稱 public final synchronized void setName(String name) //獲取執行緒的名稱 public final String getName()
Thread類中關於執行的API:
//正確的啟動一個執行緒的方法。先完成一些初始化動作,然後會通知JVM呼叫執行緒的run()方法 public synchronized void start() //只是單純地執行執行緒程式碼,並沒有啟動一個新的執行緒 public void run() //保證指定時間以內,在JVM中只有該執行緒在執行。millis是等待的毫秒數,nanos是微秒數(引數可省)。 //如果millis填0,則一直等到當前執行緒執行完畢,JVM才會排程其他執行緒執行 public final synchronized void join(long millis, int nanos) //讓該執行緒執行結束之前做下自身資源清理工作 private void exit() //當該執行緒讓出CPU,給其他執行緒一個獲取排程的機會,但是不確保一定會排程到其他執行緒上 public static native void yield(); //判斷該執行緒中斷標識是否為true,返回該標識並將標識置為false public static boolean interrupted() //判斷該執行緒中斷標識是否為true,返回該標識。其實該方法和interrupted()方法底層呼叫的都是 //isInterrupted(boolean isClearState),只不過引數不同而已 public boolean isInterrupted() //它不會中斷一個正在執行的執行緒,實際上是線上程受到阻塞時丟擲一箇中斷訊號,這樣執行緒就得以退出阻塞 //的狀態。更確切的說,如果執行緒被wait、join或sleep阻塞時,那麼它將接收到一箇中斷異常 //(InterruptedException),我們可以在異常塊裡呼叫本方法,從而提早地終結被阻塞狀態。如果執行緒沒有 //被阻塞,這時呼叫interrupt()將不起作用,僅僅是設定中斷標誌位為true的情況 public void interrupt()
-
5、幾種方法比較:
1、Thread.sleep(long millis)
一定是當前執行緒呼叫此方法,當前執行緒進入TIMED_WAITING狀態,但不釋放物件鎖,millis後執行緒自動甦醒進入就緒狀態。作用:給其它執行緒執行機會的最佳方式。
2、Thread.yield()
暫停當前正在執行的執行緒,並執行其他執行緒(可能沒有效果)。讓當前正在執行的執行緒回到可執行狀態,以允許具有相同優先順序的其他執行緒獲得執行的機會。因此,使用yield()的目的是讓具有相同優先順序的執行緒之間能夠適當的輪換執行。但是,實際中無法保證yield()達到讓步的目的,因為,讓步的執行緒可能被執行緒排程程式再次選中。
3、thread.join()/thread.join(long millis)
join作用是讓其他執行緒變為等待, t1.join();// 讓其他執行緒變為等待,直到當前t1執行緒執行完畢,才釋放。
把指定的執行緒加入到當前執行緒,可以將兩個交替執行的執行緒合併為順序執行的執行緒。比如線上程B中呼叫了執行緒A的Join()方法,直到執行緒A執行完畢後,才會繼續執行執行緒B。
4、obj.wait()
當前執行緒呼叫物件的wait()方法,當前執行緒釋放物件鎖,進入等待佇列。依靠notify()/notifyAll()喚醒或者wait(long timeout) timeout時間到自動喚醒。
5、obj.notify()
喚醒在此物件監視器上等待的單個執行緒,選擇是任意性的。notifyAll()喚醒在此物件監視器上等待的所有執行緒。
LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines), 當前執行緒進入WAITING/TIMED_WAITING狀態。對比wait方法,不需要獲得鎖就可以讓執行緒進入WAITING/TIMED_WAITING狀態,需要通過LockSupport.unpark(Thread thread)喚醒。
-
6、例題:現在有T1、T2、T3三個執行緒,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行
思路:使用join方法實現
程式碼:
public class JoinThreadDemo02 { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { public void run() { for (int i = 0; i < 20; i++) { System.out.println("t1,i:" + i); } } }); Thread t2 = new Thread(new Runnable() { public void run() { try { t1.join(); } catch (Exception e) { // TODO: handle exception } for (int i = 0; i < 20; i++) { System.out.println("t2,i:" + i); } } }); Thread t3 = new Thread(new Runnable() { public void run() { try { t2.join(); } catch (Exception e) { // TODO: handle exception } for (int i = 0; i < 20; i++) { System.out.println("t3,i:" + i); } } }); t1.start(); t2.start(); t3.start(); } }