水滴石穿--多線程入門

前記:

鄙人大三信息管理與信息系統專業,寫這一個系列的目的就是爲了好好磨磨自己的秉性,再好好從基礎一步一步走一遍,順便總結總結自己寫代碼時才過的坑,也爲下一次機會做準備。這一學年我通過傳智播客視頻課的形式學習了JAVA EE,又在假期好好看了一些計算機方向的書籍,自以爲有所收穫(其實自己是過於自信)。一次學校導員發的一個百度javaee實習信息,我就自不量力的附上了自己的簡歷去試試,人家直接電話面試一些基礎的東西都是自己做過的東西,結果忘了。答的不好所以直接涼涼,那時候起我就開始自我反省,真的是自己的太過浮躁,學習知識浮於表面不深究,儘管自己找了好多不錯的資源,但是沒有真正溶於自己的知識體系中,所以我想用剩下幾個月的時間重新走走以前學過的知識,爲明年春招實習鞏固基礎。

活不多說直奔主題,多線程面試的重點、難點,也是java的基本功所以我會在這一塊多下點功夫。

線程與進程區別:

每個正在系統上運行的程序都是一個進程。每個進程包含一到多個線程。線程是一組指令的集合,或者是程序的特殊段,它可以在程序裏獨立執行。也可以把它理解爲代碼運行的上下文。所以線程基本上是輕量級的進程,它負責在單個程序裏執行多任務。通常由操作系統負責多個線程的調度和執行。

使用線程可以把佔據時間長的程序中的任務放到後臺去處理,程序的運行速度可能加快,在一些等待的任務實現上如用戶輸入、文件讀寫和網絡收發數據等,線程就比較有用了。在這種情況下可以釋放一些珍貴的資源如內存佔用等等。

如果有大量的線程,會影響性能,因爲操作系統需要在它們之間切換,更多的線程需要更多的內存空間,線程的中止需要考慮其對程序運行的影響。通常塊模型數據是在多個線程間共享的,需要防止線程死鎖情況的發生。

總結:進程是所有線程的集合,每一個線程是進程中的一條執行路徑。

爲什麼要使用多線程?

:主要能體現到多線程提高程序效率。

舉例: 迅雷多線程下載、數據庫連接池、分批發送短信等。

多線程創建方式(3種)

1.繼承Thread類 重寫run方法

/**
 * @classDesc:線程入門,線程的創建通過繼承Thread實現,重寫run方法
 * @author: hj
 * @date:2018年12月11日 下午10:26:03
 */

public class createThread extends Thread {
	/* run方法中編寫多線程要執行的代碼 */
	@Override
	public void run() {
		System.out.println("run 方法被重寫了,正在被調用。。。");
	}

	public static void main(String[] args) {
		System.out.println("-----多線程創建開始-----");
		// 1.創建一個線程
		createThread createThread = new createThread();
		System.out.println("-----多線程創建啓動-----");
		// 2.開始執行線程 注意 開啓線程不是調用run方法,而是start方法
		createThread.start();
		System.out.println("-----多線程創建結束-----");

	}

}

允許結果:

-----多線程創建開始-----
-----多線程創建啓動-----
-----多線程創建結束-----
run 方法被重寫了,正在被調用。。。

2.實現Runnable接口,重寫run方法

/**
 * @classDesc:線程的創建(實現Runnable接口,重寫run方法)
 * @author: hj
 * @date:2018年12月11日 下午10:33:21
 */

public class CreateRunable implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("i:" + i);
		}
	}

	public static void main(String[] args) {
		System.out.println("-----多線程創建開始-----");
		// 1.創建一個線程
		CreateRunable createThread = new CreateRunable();
		// 2.開始執行線程 注意 開啓線程不是調用run方法,而是start方法
		System.out.println("-----多線程創建啓動-----");
		Thread thread = new Thread(createThread);
		thread.start();
		System.out.println("-----多線程創建結束-----");

	}

}

允許結果:-----多線程創建開始-----
-----多線程創建啓動-----
-----多線程創建結束-----
i:0
i:1
i:2
i:3
i:4
i:5
i:6
i:7
i:8
i:9

3.實現Callable<T>接口,重寫T call()方法

/**
 * @classDesc:通過實現Callable<T>接口創建線程
 * @author: hj
 * @date:2018年12月11日 下午10:42:17
 */

public class CreateCallable implements Callable<String> {

	@Override
	public String call() throws Exception {
		System.out.println("this is thread B");
		return "threadB";
	}

	public static void main(String[] args) {
		CreateCallable createC = new CreateCallable();
		FutureTask<String> futureTask = new FutureTask<>(createC);
		new Thread(futureTask).start();
		System.out.println("this is main thread!! begin...");
		try {
			System.out.println("result is: " + futureTask.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		System.out.println("this is main thread!! end");
	}

}

允許結果:

this is main thread!! begin...
this is thread B
result is: threadB
this is main thread!! end

ps:啓動線程是調用run方法還是start方法??推薦start,不過經過測試run方法也可以啓動線程。下去查了一下start方法會調run方法。 參考文章可以看看這篇文章,講的簡單易懂。

ps:使用繼承Thread類還是使用實現Runnable接口好?

 使用實現實現Runnable接口好,原因實現了接口還可以繼續繼承,繼承了類不能再繼承。如果需要返回線程執行的結果那就選擇實現callable接口。

同步與異步的概念

 

扒一張圖說明一下:(同步:代碼按照順序執行)(異步:代碼重新開一條執行路徑) 

守護線程

Java中有兩種線程,一種是用戶線程,另一種是守護線程。

 用戶線程是指用戶自定義創建的線程,主線程停止,用戶線程不會停止

守護線程當進程不存在或主線程停止,守護線程也會被停止。

 使用setDaemon(true)方法設置爲守護線程。

/**
 * @classDesc:守護線程的演示,如果是守護線程,在主線程結束時就會自動關閉,<br>
 *否則程序會一直執行,直到子進程結束
 * @author: hj
 * @date:2018年12月11日 下午11:06:40
 */

public class DaemonThread {
	public static void main(String[] args) {
		Thread thread = new Thread(new Runnable() {

			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(100);
					} catch (Exception e) {
						// TODO: handle exception
					}
					System.out.println("我是子線程...");
				}

			}
		});
		/* 注意此處,嘗試打開註釋和關閉註釋運行,體會守護進程的特性 */
		// thread.setDaemon(true);
		thread.start();
		for (int i = 0; i < 10; i++) {
			try {
				Thread.sleep(100);
			} catch (Exception e) {

			}
			System.out.println("我是主線程");
		}
		System.out.println("主線程執行完畢!");

	}

}

tips:記一個踩過的坑,在一個實習中做一個通信的任務,關閉了操作界面,但是通信線程還是沒有關閉,原因就是沒有設置成守護線程。

thread常用的API

常用線程api方法

start()

啓動線程

currentThread()

獲取當前線程對象

getID()

獲取當前線程ID      Thread-編號  該編號從0開始

getName()

獲取當前線程名稱

sleep(long mill)

休眠線程

Stop()

停止線程,

常用線程構造函數

Thread()

分配一個新的 Thread 對象

Thread(String name)

分配一個新的 Thread對象,具有指定的 name正如其名。

Thread(Runable r)

分配一個新的 Thread對象

Thread(Runable r, String name)

分配一個新的 Thread對象

多線程運行狀態

 線程從創建、運行到結束總是處於下面五個狀態之一:新建狀態、就緒狀態、運行狀態、阻塞狀態及死亡狀態。

新建狀態

   當用new操作符創建一個線程時, 例如new Thread(r),線程還沒有開始運行,此時線程處在新建狀態。 當一個線程處於新生狀態時,程序還沒有開始運行線程中的代碼

就緒狀態

一個新創建的線程並不自動開始運行,要執行線程,必須調用線程的start()方法。當線程對象調用start()方法即啓動了線程,start()方法創建線程運行的系統資源,並調度線程運行run()方法。當start()方法返回後,線程就處於就緒狀態。

     處於就緒狀態的線程並不一定立即運行run()方法,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間纔可以運行線程。因爲在單CPU的計算機系統中,不可能同時運行多個線程,一個時刻僅有一個線程處於運行狀態。因此此時可能有多個線程處於就緒狀態。對多個處於就緒狀態的線程是由Java運行時系統的線程調度程序(thread scheduler)來調度的。

運行狀態

當線程獲得CPU時間後,它才進入運行狀態,真正開始執行run()方法.

阻塞狀態

    線程運行過程中,可能由於各種原因進入阻塞狀態:
        1>線程通過調用sleep方法進入睡眠狀態;
        2>線程調用一個在I/O上被阻塞的操作,即該操作在輸入輸出操作完成之前不會返回到它的調用者;
        3>線程試圖得到一個鎖,而該鎖正被其他線程持有;
        4>線程在等待某個觸發條件;
 

死亡狀態

有兩個原因會導致線程死亡:
  1) run方法正常退出而自然死亡,
   2) 一個未捕獲的異常終止了run方法而使線程猝死。
  爲了確定線程在當前是否存活着(就是要麼是可運行的,要麼是被阻塞了),需要使用isAlive方法。如果是可運行或被阻塞,這個方法返回true; 如果線程仍舊是new狀態且不是可運行的, 或者線程死亡了,則返回false.

join()方法作用

當在主線程當中執行到t1.join()方法時,就認爲主線程應該把執行權讓給t1

/**
 * @classDesc:join用法
 * @author: hj
 * @date:2018年12月11日 下午11:56:51
 */

public class Testjoin {
	public static void main(String[] args) {
		Thread t1 = new Thread(new Runnable() {

			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					try {
						Thread.sleep(10);
					} catch (Exception e) {

					}
					System.out.println(Thread.currentThread().getName() + "i:" + i);
				}
			}
		});
		t1.start();
		// 當在主線程當中執行到t1.join()方法時,就認爲主線程應該把執行權讓給t1
		try {
			t1.join();
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		for (int i = 0; i < 10; i++) {
			try {
				Thread.sleep(10);
			} catch (Exception e) {

			}
			System.out.println("main" + "i:" + i);
		}

	}
}

優先級

現代操作系統基本採用時分的形式調度運行的線程,線程分配得到的時間片的多少決定了線程使用處理器資源的多少,也對應了線程優先級這個概念。在JAVA線程中,通過一個int priority來控制優先級,範圍爲1-10,其中10最高,默認值爲5。下面是源碼(基於1.8)中關於priority的一些量和方法。

/**
 * @classDesc: 優先級測試,設置了優先級, 不代表每次都一定會被執行。 只是CPU調度會有限分配
 * @author: hj
 * @date:2018年12月12日 上午12:00:59
 */

public class PrioritytThread implements Runnable {
	public static void main(String[] args) {
		PrioritytThread prioritytThread = new PrioritytThread();
		Thread t1 = new Thread(prioritytThread);
		Thread t2 = new Thread(prioritytThread);
		t1.start();
		// 注意設置了優先級, 不代表每次都一定會被執行。 只是CPU調度會有限分配
		t1.setPriority(10);
		t2.start();

	}

	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().toString() + "---i:" + i);
		}
	}
}

Yield方法

 

Thread.yield()方法的作用:暫停當前正在執行的線程,並執行其他線程。(可能沒有效果)

yield()讓當前正在運行的線程回到可運行狀態,以允許具有相同優先級的其他線程獲得運行的機會。因此,使用yield()的目的是讓具有相同優先級的線程之間能夠適當的輪換執行。但是,實際中無法保證yield()達到讓步的目的,因爲,讓步的線程可能被線程調度程序再次選中。

結論:大多數情況下,yield()將導致線程從運行狀態轉到可運行狀態,但有可能沒有效果。

爲了方便大家總結,以後都會在後面放幾道面試題。

面試題

進程與線程的區別?

:進程是所有線程的集合,每一個線程是進程中的一條執行路徑,線程只是一條執行路徑。

多線程創建方式?

  :繼承ThreadRunnable 接口或callable<T>。

是繼承Thread類好還是實現Runnable接口好?

:Runnable接口好,因爲實現了接口還可以繼續繼承。繼承Thread類不能再繼承。

有T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行  

/** 
 * @classDesc: 面試題:有T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行
 * 使用join()方法
 * @author: hj
 * @date:2018年12月11日 下午11:54:49
 */

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) {
				}
				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) {
				}
				for (int i = 0; i < 20; i++) {
					System.out.println("t3,i:" + i);
				}
			}
		});
		t1.start();
		t2.start();
		t3.start();
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章