1. 程式人生 > >多執行緒入門(一)

多執行緒入門(一)

  • 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();
	}
}