進程:正在運行的程序。也就是表明了程序鎖佔用的內存區域。
線程:線程(thread)是操做系統可以進行運算調度的最小單位;一個進程能夠開啓多個線程。
進程和線程的區別:
一個軟件運行至少依賴一個進程。
一個進程的運行至少依賴一個線程。面試
多線程:多線程擴展了多進程的概念,使得同一個進程能夠同時併發處理多個任務。編程
線程生命週期,總共有五種狀態:
1) 新建狀態(New):當線程對象對建立後,即進入了新建狀態,如:Thread t = new MyThread();
2) 就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處於就緒狀態的線程,只是說明此線程已經作好了準備,隨時等待CPU調度執行,並非說執行了t.start()此線程當即就會執行;
3) 運行狀態(Running):當CPU開始調度處於就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。注:就緒狀態是進入到運行狀態的惟一入口,也就是說,線程要想進入運行狀態執行,首先必須處於就緒狀態中;
4) 阻塞狀態(Blocked):處於運行狀態中的線程因爲某種緣由,暫時放棄對CPU的使用權,中止執行,此時進入阻塞狀態,直到其進入到就緒狀態,纔有機會再次被CPU調用以進入到運行狀態;
5) 根據阻塞產生的緣由不一樣,阻塞狀態又能夠分爲三種:設計模式
a) 等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態; b) 同步阻塞:線程在獲取synchronized同步鎖失敗(由於鎖被其它線程所佔用),它會進入同步阻塞狀態; c) 其餘阻塞:經過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入就緒狀態。
6) 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。安全
建立對象:多線程
Thread() 分配新的 Thread 對象。 Thread(Runnable target) 分配新的 Thread 對象。 Thread(Runnable target, String name) 分配新的 Thread 對象。 Thread(String name) 分配新的 Thread 對象。
經常使用方法:併發
long getId() 返回該線程的標識符。 String getName() 返回該線程的名稱。 void run() 若是該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;不然,該方法不執行任何操做並返回。 void setName(String name) 改變線程名稱,使之與參數 name 相同。 static void sleep(long millis) 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行), void start() 使該線程開始執行;Java 虛擬機調用該線程的 run 方法。 void stop() 終止線程。
測試:app
package cn.tedu.thread; //測試 Thread類 public class Test2_Thread { public static void main(String[] args) { //1,建立對象 MyThread t = new MyThread();//新建狀態 //2,開啓線程 // t.run(); t.setName("線程1");//修改線程名 t.start();//可運行狀態 //3,run()和start()有什麼區別? //run()和start()都能執行任務,可是run()執行時就只是一個普通的方法調用沒有多線程併發搶佔的效果 //--模擬多線程-- MyThread t2 = new MyThread(); // t2.run(); t.setName("線程2");//修改線程名 t2.start(); /* 4,多線程的執行結果具備隨機性 Thread-0---0 Thread-1---0 Thread-0---1 Thread-1---1 Thread-0---2 Thread-1---2 Thread-1---3*/ } } //1,繼承Thread類,實現多線程編程 class MyThread extends Thread { //2,在多線程編程裏,把全部的業務放入--重寫的run()裏--generate--override methods.. //--需求:重複的打印10次線程名稱 @Override public void run() {//運行狀態 for (int i = 0; i < 10; i++) {//fori System.out.println(super.getName()+"---"+i);//getName()--獲取線程名稱 } }//結束狀態 }
概述:若是本身的類已經extends另外一個類,就沒法多繼承,此時,能夠實現一個Runnable接口。ide
經常使用方法:性能
void run() 使用實現接口 Runnable 的對象建立一個線程時,啓動該線程將致使在獨立執行的線程中調用對象的 run 方法。
測試:測試
package cn.tedu.thread; //測試 Runnable接口 public class Test3_Runnable { public static void main(String[] args) { //1,建立目標對象 MyRunnable target = new MyRunnable(); //2,把要執行的目標 和Thread綁定關係 Thread t = new Thread(target); t.setName("豬豬俠"); //3,啓動線程 t.start(); //4,模擬多線程 Thread t2 = new Thread(target); t2.setName("蜘蛛俠"); t2.start(); /* 5,多線程的隨機性 Thread-0===0 Thread-0===1 Thread-1===0 Thread-1===1 Thread-1===2 Thread-1===3 Thread-1===4 Thread-0===2 Thread-0===3 */ } } //1,實現Runnable接口,實現多線程編程 class MyRunnable implements Runnable{ //2,若是有本身的業務,須要把業務 -- 放在重寫的run()裏 //--需求:打印10次線程名稱 @Override public void run() { for (int i = 0; i < 20; i++) { //Thread.currentThread()--獲取正在執行任務的線程對象 //getName()--獲取線程的名字 //3,Thread.currentThread().getName() -- 獲取當前執行任務的線程對象的 名字 System.out.println( Thread.currentThread().getName() +"==="+i ); } } }
建立步驟:
1.建立Callable接口的實現類,並重寫call()方法,該方法做爲線程的執行體,並有返回值。
2.建立Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
3.建立Thread,調用start()方法。
4.但是有FutureTask.get()獲取call()的返回值。
public class TestCallable { public static void main(String[] args) throws InterruptedException, ExecutionException { CallableDemo callableDemo = new CallableDemo(); FutureTask futureTask = new FutureTask(callableDemo); new Thread(futureTask).start();//啓動線程對象,並啓動線程 //Integer a = (Integer) futureTask.get(); //經過get()獲取返回值 for (int j = 0; j < 10; j++) { System.out.println("main:"+j); } } } class CallableDemo implements Callable{ @Override public Integer call() throws Exception { int i =0; for (; i < 10; i++) { System.out.println("線程:"+i); } return i; } }
結果:
main:0 線程:0 main:1 線程:1 main:2 線程:2 main:3 線程:3 線程:4 線程:5 線程:6 線程:7 線程:8 線程:9 main:4 main:5 main:6 main:7 main:8 main:9
線程池是池化思想的一種應用,都是經過複用對象,以減小因建立和釋放對象所帶來的資源消耗,進而來提高系統性能。
ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor之間的關係:
推薦使用ThreadPoolExecutor建立線程
// 建立線程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); // 向線程池提交任務 threadPool.execute(new Runnable() { @Override public void run() { ... // 線程執行的任務 } }); // 關閉線程池 threadPool.shutdown(); // 設置線程池的狀態爲SHUTDOWN,而後中斷全部沒有正在執行任務的線程 threadPool.shutdownNow(); // 設置線程池的狀態爲 STOP,而後嘗試中止全部的正在執行或暫停任務的線程,並返回等待執行任務的列表
4種線程池的建立方法:
線程通訊的兩種簡單模型,共享內存和消息傳遞。
使用volatile關鍵字定義全局變量,當變量發生變化時,線程可以感應並執行相應的業務。採用了共享內存的思想。
在synchronized修飾的同步方法或代碼塊中使用Object類提供的wait(),notify()和notifyAll()3個方法進行線程通訊。
1.wait():致使當前線程等待,直到其餘線程調用該同步監視器的notify()方法或notifyAll()方法來喚醒該線程。wait()釋放鎖。 2.notify():喚醒在此同步監視器上等待的單個線程。notify()不釋放鎖。 3.notifyAll():喚醒在此同步監視器上等待的全部線程。
悲觀鎖:仍是像它的名字同樣,對於併發間操做產生的線程安全問題持悲觀狀態,悲觀鎖認爲競爭老是會發生,所以每次對某資源進行操做時,都會持有一個獨佔的鎖,就像synchronized,無論三七二十一,直接上了鎖就操做資源了。
樂觀鎖:就像它的名字同樣,對於併發間操做產生的線程安全問題持樂觀狀態,樂觀鎖認爲競爭不老是會發生,所以它不須要持有鎖,將比較-替換這兩個動做做爲一個原子操做嘗試去修改內存中的變量,若是失敗則表示發生衝突,那麼就應該有相應的重試邏輯。
synchronized 互斥鎖(悲觀鎖,有罪假設)
採用synchronized修飾符實現的同步機制叫作互斥鎖機制,它所得到的鎖叫作互斥鎖。每一個對象都有一個monitor(鎖標記),當線程擁有這個鎖標記時才能訪問這個資源,沒有鎖標記便進入鎖池。任何一個對象系統都會爲其建立一個互斥鎖,這個鎖是爲了分配給線程的,防止打斷原子操做。每一個對象的鎖只能分配給一個線程,所以叫作互斥鎖。
ReentrantReadWriteLock 讀寫鎖(樂觀鎖,無罪假設)
ReentrantLock是排他鎖,排他鎖在同一時刻僅有一個線程能夠進行訪問,實際上獨佔鎖是一種相對比較保守的鎖策略,在這種狀況下任何「讀/讀」、「讀/寫」、「寫/寫」操做都不能同時發生,這在必定程度上下降了吞吐量。然而讀操做之間不存在數據競爭問題,若是」讀/讀」操做可以以共享鎖的方式進行,那會進一步提高性能。所以引入了ReentrantReadWriteLock,顧名思義,ReentrantReadWriteLock是Reentrant(可重入)Read(讀)Write(寫)Lock(鎖),咱們下面稱它爲讀寫鎖。
讀寫鎖內部又分爲讀鎖和寫鎖,讀鎖能夠在沒有寫鎖的時候被多個線程同時持有,寫鎖是獨佔的。讀鎖和寫鎖分離從而提高程序性能,讀寫鎖主要應用於讀多寫少的場景。
把共享資源,用鎖鎖起來,誰有鑰匙,誰去用共享資源,沒鑰匙的就排隊等待。
用法:
經過synchronized關鍵字實現同步鎖
用在方法上:synchronized public StringBuffer append(String str) 用在代碼塊上:synchronized( 鎖對象 ){ 須要同步的代碼;}
用在方法上,使用的鎖對象是默認的this.;
用在代碼塊上,鎖的對象是任意的。
public class Test6_Singleton { public static void main(String[] args) { MySingle m = MySingle.getSingle(); MySingle m2 = MySingle.getSingle(); //==若是比較的是兩個引用類型的變量?比較的是地址值 System.out.println(m==m2); } } //建立本身的單例程序 class MySingle { //1,私有化構造方法,不讓外界隨意new private MySingle() {} //2,在類的內部,提供一個已經建立好的對象 //static是由於 靜態資源getSingle()只能調用靜態 static private MySingle single = new MySingle(); //3,對外提供一個全局訪問點 //static 是由於,想要訪問get()能夠建立對象訪問,可是目前已經不容許建立對象了。只能經過類名調用,就得修飾。 static public MySingle getSingle(){ //把內部建立好的對象返回調用位置 return single; } }
//面試重點: //一、延遲加載思想:是指不會第一時間就把對象建立好來佔用內存,而是何時用何時建立 //二、線程安全問題:是指共享資源有線程併發的數據隱患,加同步鎖,鎖方法,也能夠鎖代碼塊 public class Test7_Single2 { public static void main(String[] args) { MySingleton my = MySingleton.getMy(); MySingleton my2 = MySingleton.getMy(); System.out.println(my==my2); } } // 建立類 class MySingleton { // 1,私有化構造方法 -- 控制外界new的權利 private MySingleton() { } // 2,在類的內部建立好對象 -- 延遲加載!! static private MySingleton my; // static Object obj = new Object(); // 3,提供全局訪問點 // 問題:程序中有共享資源my,而且有多條語句(3條)操做了共享資源,此時,my共享資源必定會存在多線程編程的數據安全隱患 // 解決方案就是加 同步鎖 。 //若是用同步代碼塊須要肯定鎖的位置? 鎖的對象?因爲方法中的全部代碼都被同步了,能夠直接變成同步方法。 synchronized static public MySingleton getMy() { // synchronized (obj) {//同步代碼塊:靜態區域內,不能是用this關鍵字?由於加載的前後順序問題 if (my == null) {// 說明沒new過,保存的是默認值null my = new MySingleton();// 須要時纔會建立對象,不須要也不會提早就開始佔用內存。 } return my; // } } }