多線程

多線程快速入門

課程目標
線程與進程
爲什麼要使用多線程?
多線程應用場景
多線程創建方式
練習題
線程生命週期
面試總結
練習題
線程與進程區別
每個正在系統上運行的程序都是一個進程。每個進程包含一到多個線程。線程是一組指令的集合,或者是程序的特殊段,它可以在程序裏獨立執行。也可以把它理解爲代碼運行的上下文。所以線程基本上是輕量級的進程,它負責在單個程序裏執行多任務。通常由操作系統負責多個線程的調度和執行。
使用線程可以把佔據時間長的程序中的任務放到後臺去處理,程序的運行速度可能加快,在一些等待的任務實現上如用戶輸入、文件讀寫和網絡收發數據等,線程就比較有用了。在這種情況下可以釋放一些珍貴的資源如內存佔用等等。
如果有大量的線程,會影響性能,因爲操作系統需要在它們之間切換,更多的線程需要更多的內存空間,線程的中止需要考慮其對程序運行的影響。通常塊模型數據是在多個線程間共享的,需要防止線程死鎖情況的發生。
總結:進程是所有線程的集合,每一個線程是進程中的一條執行路徑。
爲什麼要使用多線程?
思考生活問題?
現在有1000頓的水,目前只有小明一個人去打水,但是小明每小時打水200頓,現在要求一小時內把水全部打完,請問怎麼解決?
如果小明一個人將水全部打完需要五個小時。
  解決辦法:
在加四個人同時打水,分別爲小軍、小紅、小玲、小小,加上小明一共五個人同時去打水,五個人每小時打水200千克,那麼一小時後就可以完成打完一頓水。
總結:多線程的好處提高程序的效率。
多線程應用場景?
答:主要能體現到多線程提高程序效率。
舉例: 迅雷多線程下載、分批發送短信等。
多線程創建方式
第一種繼承Thread類 重寫run方法

 
代碼:	   
/**
 * 
 * @classDesc: 功能描述:(創建多線程例子-Thread類 重寫run方法)
 * @author: 
 * @version: v1.0
 * @copyright:
 */
class CreateThread extends Thread {
	// run方法中編寫 多線程需要執行的代碼
	publicvoid run() {
		for (inti = 0; i< 10; i++) {
			System.out.println("i:" + i);
		}
	}
}
publicclass ThreadDemo {

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

}
	 

允許結果:
 
調用start方法後,代碼並沒有從上往下執行,而是有一條新的執行分之
。
注意:畫圖演示多線程不同執行路徑。
 
第二種實現Runnable接口,重寫run方法
代碼:
 
/**
 * 
 * @classDesc: 功能描述:(創建多線程例子-Thread類 重寫run方法)
 * @author: 
 * @version: v1.0
 * @copyright:
 */
class CreateRunnable implements Runnable {

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

}

/**
 * 
 * @classDesc: 功能描述:(實現Runnable接口,重寫run方法)
 * @author: 
 * @version: v1.0
 * @copyright:
 */
publicclass ThreadDemo2 {
	publicstaticvoid 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("-----多線程創建結束-----");
	}
}
	 
第三種使用匿名內部類方式
 
	 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("-----多線程創建結束-----");	 
使用繼承Thread類還是使用實現Runnable接口好?
 使用實現實現Runnable接口好,原因實現了接口還可以繼續繼承,繼承了類不能再繼承。
啓動線程是使用調用start方法還是run方法?
開始執行線程 注意 開啓線程不是調用run方法,而是start方法
調用run知識使用實例調用方法。

獲取線程對象以及名稱
 
常用線程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對象	 
守護線程
 Java中有兩種線程,一種是用戶線程,另一種是守護線程。
 用戶線程是指用戶自定義創建的線程,主線程停止,用戶線程不會停止
守護線程當進程不存在或主線程停止,守護線程也會被停止。
 使用setDaemon(true)方法設置爲守護線程
 
**
 * 
 * 什麼是守護線程? 守護線程 進程線程(主線程掛了) 守護線程也會被自動銷燬.
 * 
 * @classDesc: 功能描述:(守護線程)
 * @author: 
 * @createTime: 2017年8月20日 下午8:55:58
 * @version: v1.0
 * @copyright:
 */
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("主線程執行完畢!");
	}

}
	 
多線程運行狀態
 
 線程從創建、運行到結束總是處於下面五個狀態之一:新建狀態、就緒狀態、運行狀態、阻塞狀態及死亡狀態。
新建狀態
   當用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()方法作用
join作用是讓其他線程變爲等待, 	t1.join();// 讓其他線程變爲等待,直到當前t1線程執行完畢,才釋放。
thread.Join把指定的線程加入到當前線程,可以將兩個交替執行的線程合併爲順序執行的線程。比如在線程B中調用了線程A的Join()方法,直到線程A執行完畢後,纔會繼續執行線程B。
需求:
創建一個線程,子線程執行完畢後,主線程才能執行。
 

class JoinThread implements Runnable {

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

/**
 * 
 * @classDesc: 功能描述:(Join方法)
 * @author: 
 * @createTime: 2017年8月20日 下午9:23:30
 * @version: v1.0
 * @copyright:
 */
public class JoinThreadDemo {

	public static void main(String[] args) {
		JoinThread joinThread = new JoinThread();
		Thread t1 = new Thread(joinThread);
		Thread t2 = new Thread(joinThread);
		t1.start();
		t2.start();
		try {
       //其他線程變爲等待狀態,等t1線程執行完成之後才能執行join方法。
			t1.join();
		} catch (Exception e) {

		}
		for (int i = 0; i < 100; i++) {
			System.out.println("main ---i:" + i);
		}
	}

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

 
class PrioritytThread implements Runnable {

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

/**
 * 
 * @classDesc: 功能描述:(Join方法)
 * @author: 
 * @createTime: 2017年8月20日 下午9:23:30
 * @version: v1.0
 * @copyright:
 */
public class ThreadDemo4 {

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

}
	 
Yield方法

Thread.yield()方法的作用:暫停當前正在執行的線程,並執行其他線程。(可能沒有效果)
yield()讓當前正在運行的線程回到可運行狀態,以允許具有相同優先級的其他線程獲得運行的機會。因此,使用yield()的目的是讓具有相同優先級的線程之間能夠適當的輪換執行。但是,實際中無法保證yield()達到讓步的目的,因爲,讓步的線程可能被線程調度程序再次選中。
結論:大多數情況下,yield()將導致線程從運行狀態轉到可運行狀態,但有可能沒有效果。

多線程分批處理數據
需求:目前螞蟻課堂有10萬個用戶,現在螞蟻課堂需要做活動,給每一個用戶發送一條祝福短信。
爲了提高數程序的效率,請使用多線程技術分批發送據。
每開一個線程,都會佔用CPU資源
服務器(電腦)配置 CPU 核數
建立項目名稱:itmayiedu_thread_batch

新建用戶實體類
 
package com.itmayiedu.enity;
/**
 * 
 * @classDesc: 功能描述:(用戶實體類)
 * @author: 
 * @createTime: 2017年8月7日 下午9:01:20
 * @version: v1.0
 * @copyright:上海每特教育科技有限公司
 */
publicclass UserEntity {
private String userId;
private String userName;
public String getUserId() {
returnuserId;
}
publicvoid setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}

publicvoid setUserName(String userName) {
this.userName = userName;
}
}
	 
建立多線程UserThread 執行發送短信
 
Class UserThreadextends Thread {
	private List<UserEntity>list;
/**
	 * 通過構造函數 傳入每個線程需要執行的發送短信內容
	 * 
	 * @param list
	 */
	public UserThread(List<UserEntity>list) {
		this.list = list;
	}
/**
	 * 
	 * @methodDesc: 功能描述:(分批發送短信)
	 * @author: 
	 * @param:
	 * @createTime:2017年8月7日 下午9:09:54
	 * @returnType: void
	 * @copyright:
	 */
	publicvoid run() {
		for (UserEntity userEntity : list) {
			System.out.println("threadName:" + Thread.currentThread().getName() + "-學員編號:" + userEntity.getUserId()
					+ "---學員名稱:" + userEntity.getUserName());
			// 調用發送短信具體代碼
		}
	}
}
	 
初始化數據
 
/**
	 * 
	 * @methodDesc: 功能描述:(初始化數據)
	 * @author: 
	 * @param:
	 * @createTime:2017年8月7日 下午9:16:53
	 * @returnType: void
	 * @copyright:
	 */
	publicstatic List<UserEntity> init() {
		List<UserEntity>list = new ArrayList<UserEntity>();
		for (inti = 1; i<= 140; i++) {
			UserEntity userEntity = new UserEntity();
			userEntity.setUserId("userId" + i);
			userEntity.setUserName("userName" + i);
			list.add(userEntity);
		}
		returnlist;

	}
	 
計算分頁工具類
 
public class ListUtils {
	/**
	 * 
	 * @methodDesc: 功能描述:(list 集合分批切割)
	 * @author: 
	 * @param: @param
	 *             list
	 * @param: @param
	 *             pageSize
	 * @param: @return
	 * @createTime:2017年8月7日 下午9:30:59
	 * @returnType:@param list 切割集合
	 * @returnType:@param pageSize 分頁長度
	 * @returnType:@return List<List<T>> 返回分頁數據
	 * @copyright:
	 */
	
	static public<T> List<List<T>> splitList(List<T> list, int pageSize) {
		int listSize = list.size();
		int page = (listSize + (pageSize - 1)) / pageSize;
		List<List<T>>listArray = new ArrayList<List<T>>();
		for (int i = 0; i<page; i++) {
			List<T>subList = new ArrayList<T>();
			for (int j = 0; j<listSize; j++) {
				int pageIndex = ((j + 1) + (pageSize - 1)) / pageSize;
				if (pageIndex == (i + 1)) {
					subList.add(list.get(j));
				}
				if ((j + 1) == ((j + 1) * pageSize)) {
					break;
				}
			}
			listArray.add(subList);
		}
		return listArray;
	}
}
	 
實現發送短信
 
Public staticvoid main(String[] args) {
		// 1.初始化用戶數據
		List<UserEntity>listUserEntity = init();
		// 2.計算創建創建多少個線程並且每一個線程需要執行“分批發送短信用戶”
		// 每一個線程分批跑多少
		intuserThreadPage = 50;
		// 計算所有線程數
		List<List<UserEntity>>splitUserList = ListUtils.splitList(listUserEntity, userThreadPage);
		intthreadSaze = splitUserList.size();
		for (inti = 0; i<threadSaze; i++) {
			List<UserEntity>list = splitUserList.get(i);
			UserThread userThread = new UserThread(list);
			// 3.執行任務發送短信
			userThread.start();
		}

	}
	 

作業題
現在有T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行  
代碼:
 
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();
	}
}
	 




面試題
進程與線程的區別?
答:進程是所有線程的集合,每一個線程是進程中的一條執行路徑,線程只是一條執行路徑。
爲什麼要用多線程?
 答:提高程序效率
多線程創建方式?
  答:繼承Thread或Runnable 接口。
是繼承Thread類好還是實現Runnable接口好?
答:Runnable接口好,因爲實現了接口還可以繼續繼承。繼承Thread類不能再繼承。
你在哪裏用到了多線程?
答:主要能體現到多線程提高程序效率。
舉例:分批發送短信、迅雷多線程下載等。


備註單詞
Callable

 

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