黑馬程序員_【總結】_多線程知識梳理1

多線程知識梳理1


---------- android培訓 java培訓、期待與您交流! ---------- 

---------------------------------------------------------------------------------------------------------------------------------------------
1、線程需要  extends Thread   OR    implements Runnable  
2、implements 方式線程更優 避免了單繼承 適合操作共享數據
3、線程必須覆蓋 run 方法 且必須是 public
4、線程獲取 執行權,執行時間 完全隨機。
5、Static Thread currentThread() 當前線程 引用 標準 this
6、功能代碼,都存放在 run 方法當中
7、同步代碼塊 讓多條操作共享數據的語句只讓一個線程來執行,完成前其他線程不可參與
8、同步的前提: 1、必須2個或以上的線程 
      2、必須是多線程使用同一把鎖
9、同步代碼塊: 優點:解決了多線程的安全問題
弊端:多個線需要判斷鎖,較爲消耗資源
10、如何使用同步代碼快?
1、明確那些代碼是多線程運行代碼
2、明確共享數據
凡是是出現2次的變量,中間加一句睡覺,必能看見問題。
3、明確多線程運行代碼中那些語句是操作共享數據的。
11、同步代碼塊 鎖 當前對象引用:this。
12、非靜態同步代碼方法、函數 鎖 當前對象引用:this。
13、靜態同步代碼方法、函數 鎖: 當前類字節碼文件對象  類.class
14、不論是靜態還是非靜態,不論是組合還是非組合,鎖最好選定爲該類中唯一的 
15、死鎖  通常是同步代碼中籤到同步代碼,並別持有的鎖,不相同。
16、懶漢式-- 延遲加載--單列設計模式
特點:延遲加載、多線程時容易出安全問題、
同步代碼塊或者同步方法可解決安全問題,但效率不高
同步代碼塊加上雙重if判斷,可解決效率問題
--------------------------------------------------------------------
【0】
線程常識

進程:在日常電腦應用中,每一個應用程序至少有一個進程。
這個進程中至少包含了一個或者多個獨立的控制單元。

線程:線程就是這個獨立的每一個單元。

線程並不難理解,日常生活中非常普及,
比如用下載器下載數據,使用了多個線程,同時下載
比如QQ聊天,同時打開多個窗口,和多個夥伴兒聊天。

線程的存在和由來,都是爲了提高效率。
main 方法是一個主線程,假設只用這一個線程,完成上面的小例子,就會非常的消耗時間。
【1】
【1-1】
創建一個線程 extends Thread

1.繼承 Thread----   
實現Runnable 的線程更適合資源共享。
2.必須覆蓋 run 方法。且必須是public
run方法其實就是功能代碼的軀殼。
3.構造方法可以通過super 指向父類。
4.啓動線程是 start 方法,直接使用run 就不是線程的執行,而是方法的調用了。
至於 start 肯定是底層調用了某系東西,作爲新手就不再深入了。
-------------------------------------------------
public class ThreadTest {
	public static void main(String[] args) {
		newThread td1=new newThread("線程A");
		td1.start();

		new Thread(new newThread("-----------b")).start();
	}
}
class newThread extends Thread
{
	newThread(String name){
		super(name);
	}
	public void run(){
		for(int i = 0 ;i<50 ; i++){
			System.out.println(this.getName());
		}
	}
}
-------------------------------------------------
1、
根據多次運行,知道 
每次運行結果不一樣,
這裏涉及到 CPU機制問題。CPU在同一時刻,只能執行一個程序。
並迅速的在不同程序間做着切換,由於速度太快,而給人以同時運行的效果。

線程的隨機性:
線程的運行,彷彿就是相互爭奪 CPU的執行權利。 誰搶到執行誰,至於執行時間,看CPU。


在Java中,所有的線程都是同時啓動的, 但是什麼時候執行那個個,執行多久,都是CPU來決定
在Java中,每次程序的運行至少啓動了2個線程,1是main主線程,2是垃圾回收線程。
在Java中,只要前臺有一個線程在運行,整個Java程序進程不會消失,可以設置一個後臺程序,
即使Java進程小時,後臺線程依然能夠運行。
2、
線程的幾個方法
Static Thread currentThread() 同this  當前線程的引用。

通常 定義好一個線程後,使用內部類方式書寫:
new Thread(new threadName()).start()

setName() 更改線程名
getName() 獲取線程名
start() 啓動
sleep() 睡眠 sleep() 用處比較廣泛
Thread.sleep(6000); 設置等待6000毫秒。然後幹什麼。
wait() 等待
notify() 喚醒
stop() 停止

【1-2】
// 模擬賣票小程序
//繼承 Thread
class MyThreadT
{
	public static void main(String[] args) 
	{
		TicketDemo t1 = new TicketDemo();
		TicketDemo t2 = new TicketDemo();
		TicketDemo t3 = new TicketDemo();
		t2.start();
		t3.start();
		t1.start();
	}
}
class TicketDemo extends Thread
{
	private int  tick = 100;
	public void run(){
		while(true){
			if(tick>0){
				System.out.println(this.getName()+"..."+tick--);
			}
		}
	}
}


//2、實現	Runnable
class MyThreadR
{
	public static void main(String[] args) 
	{
		TicketDemo t = new TicketDemo();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
	}
}
class TicketDemo implements Runnable
{
	private int tick = 100;
	public void run(){
		while(true){
			if(tick>0){
				System.out.println(Thread.currentThread().getName()+"..."+tick--);
			}
		}
	}
}
相同的代碼,只是 一個通過繼承,一個是通過實現 來達到多線程的作用。

發現
繼承 Thread 的線程  每一條線程都在從1到100的賣票,也就是300張票。數據
實現 Runnable 的線程  則是才賣100張票

結論:
1、避免了單繼承的侷限性
2、更適合多個線程去處理同一個資源。
建議使用 實現方式的 線程

區別就是 功能代碼都存放在對應的run方法中。

【2】
線程的5個狀態---線程的安全問題。

1、創建狀態 2、運行狀態 3、阻塞狀態 4、消亡狀態 5、凍結狀態
當多個線程被創建時,在內存中會有這樣一個現象:
1、都在具備運行資格,但沒有執行權
2、其中一個有了執行權利,進入執行,可能會:而其他現在發生着類似情況。
1、直接運行完畢,也就是結束了。
2、運行一半 sleep
3、wait了 
當屬於2,3時候,當 sleep,世間結束,當 wait被 notify() 喚醒後:
1、繼續獲取到執行權利執行
2、返回到等待狀態,擁有資格沒有權利
4、運行結束或者被 stop  線程消亡
(配圖)



就上述 案例中,
	public void run(){
		while(true){
			if(tick>0){
				System.out.println(Thread.currentThread().getName()+"..."+tick--);
			}
		}
	}
由於 線程的執行權是爭取的,而執行之間是CPU 決定的。邏輯上完全可能出現下來情況:
當 tick=1的時候:
1線程  判斷 tick>0  進入   然後臥倒了。 這時2 線程進入 tick>0   然後又臥倒了。
然後3線程 同樣進來了, 這時候也臥倒了,   
三個線程都掛這裏後,1線程起來了,這時候不會在判斷而直接打印了當前的tick--   也就是0
接着2 線程 起來了,也不會在判斷而 打直接印了當前的tick--  也就是-1了
接着3 線程 起來了,同樣不會在判斷而 打直接印了當前的tick--  也就是-2了

打印出0 的時候 程序已經就出現問題了。

if(tick>0){
		try{Thread.sleep(10);}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"..."+tick--);
	}
只需要在打印前,讓該線程睡上10毫秒,即可達到上述狀況

也就是說,涉及到了 線程的安全問題。

問題的產生在於:當一個線程對多條語句只操作一部分的時候,另一個線程參與執行後,導致共享數據的錯亂
解決辦法就是: 對多條操作共享數據的語句,只讓一個線程來執行完成,完成前其他線程不可參與。


Java 對於多線程的安全問題提供了對應的專業技術:
【3】
同步代碼塊:

synchronized(對象){
需要被同步的代碼。
}

那麼上面的代碼就更改爲:
class TicketDemo implements Runnable
{
	private int tick = 100;
	public void run(){
		while(true){
			synchronized(this)
			{
				if(tick>0){
				try{Thread.sleep(10);}catch(Exception e){}
				System.out.println(Thread.currentThread().getName()+"..."+tick--);
				}
			}
		}
	}
}
這時候,既時寫Thread.sleep(10000) 睡上一天,也不會在出現問題了。

對象如同鎖,持有鎖的線程可以在同步中執行,
沒有持有鎖,即使線程獲取到了CPU執行權,因爲沒有鎖,也無法進去
聯想一下 老師 ,火車的  衛生間  例子。
裏面的人不出來,外面的人是進不去的。 鎖,生動的解釋了這一現象。

同步代碼 有2個前提:
1、必須2個或以上的線程
2、必須是多個線程使用同一把鎖

必須保證同步中只能有一個線程在運行。

優點:解決了多線程的安全問題
弊端:多個線需要判斷鎖,較爲消耗資源

如何使用同步代碼快?
1、明確那些代碼是多線程運行代碼
2、明確共享數據
凡是是出現2次的變量,中間加一句睡覺,必然能看見問題,也就是說需要同步在一起 如下:
3、明確多線程運行代碼中那些語句是操作共享數據的。

同步函數、同步方法
class bank 
{
	public static void main(String[] args) 
	{
		Cus b = new Cus();
		new Thread(b).start();
		new Thread(b).start();
	}
}
class BankT
{
	private int sum ;
	public synchronized void add(int n)
	{
		sum = sum+n;
		try{Thread.sleep(10);}catch(Exception e){}
		System.out.println(sum);
	}
}
class Cus implements Runnable
{
	private BankT b = new BankT();
	public void run(){	
		for(int i  = 0 ;i<3 ;i++){
			b.add(100);
		}
	}
}
相比同步代碼塊而言,同步方法,更簡潔一些。持有的鎖爲 當前對象的引用。

鎖 具有唯一性,那麼 使用的時候,就使用當前引用,作爲鎖,避免出現問題

同步方法的鎖是 this
同步代碼塊的鎖 也用this ,

當同步方法和同步代碼塊組合應用的時候,纔不會出現問題
出現問題肯定是2個前提。

當同步方法爲靜態的時候,鎖不再是this的,   靜態方法中是沒有this的。
那麼鎖是誰呢?

因爲有 static 進內存的時候是沒有當前對象,所以沒有this
但是一定有當前類對應的字節碼文件對象
該對象的類型是  Class
class mair
{
	public static void main(String[] args) 
	{
		TicketDemo t = new TicketDemo();
		new Thread(t).start();
		try{Thread.sleep(10);}catch(Exception e){}
		t.flag = false;
		new Thread(t).start();
	}
}
class TicketDemo implements Runnable
{
	//private int tick = 100;  【A】對應非靜態同步方法
	private static int tick = 100;
	boolean flag= true;
	public void run(){
		if(flag){
			//同步代碼塊
			while(true){
				//synchronized(this) //【C】對應A  
				synchronized(TicketDemo.class)
				{
					if(tick>0){
					try{Thread.sleep(10);}catch(Exception e){}
					System.out.println(Thread.currentThread().getName()+"...code"+tick--);
					}
				}
			}
		}else{
			//同步方法
			while(true){
				this.show();
			}
		}		
	}
	//public synchronized void show() //【B】對應A  [abc 爲非靜態同步方法組合方式]  
	public static synchronized void show()
	{
		if(tick>0){
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"...show"+tick--);
		}
	}
}
【重點】總結:
當 靜態同步方法和同步代碼塊組合使用的時候,鎖 是 該類字節碼對象
當 非靜態同步方法和同步代碼塊組合使用時候,鎖 是 當前對象引用 this


隨便提一下 經常面試的【單列設計模式】題目

問;懶漢式和惡漢式有什麼不同
懶漢式特點是 對象的延遲加載
問:懶漢式,有沒有問題
有,多線程訪問時容易出現安全問題。
問:怎麼解決,
可以加同步來解決,
加同步代碼塊,或者同步方法 可以解決安全問題,但是效率比較低效
通過雙重判斷,結合同步代碼塊 ,可以解決效率問題

問:懶漢式的鎖是那個
該類的字節碼文件對象。

【重點】//懶漢式 常考  也稱爲 延遲加載的單例設計模式
class Single {
	private static Single s = null;
	private Single(){}
	public static Single getInstance(){
		if(s == null){
			synchronized(Single.class){
				if(s == null)
					s = new Single();
			}
		}
		return s;
	}
}
假設123,線程,1進去,判斷 null ,進入同步代碼塊,臥倒了。
2進去,同樣 null ,但是 2沒有鎖,進不去,
1醒了創建實例對象 結束,
2醒了,進入鎖,再判斷, 不爲 null ,出去了,
3進入,一判斷,不爲null */
惡漢式,常用方式
class Single{
	private static final Single s = new Single();
	private Single(){};
	public static Single getInstance(){
		return s;
	}
}
死鎖
同步中嵌套同步
//實現一個死鎖: 同步中嵌套鎖
class ThreadS implements Runnable
{
	private boolean falg;
	ThreadS(boolean falg){
		this.falg = falg;
	}
	public  void run(){
		if(falg){
			synchronized(mySuo.A){
				System.out.println(" if......A");
				synchronized(mySuo.B){
					System.out.println(" if......BBB");
				}
			}
		}
		else{
			synchronized(mySuo.B){
				System.out.println(" else.....BBB");
				synchronized(mySuo.A){
					System.out.println(" else.....AAA");
				}
			}
		}
	}
}

class  mySuo // 鎖
{
	static Object A = new Object();//靜態修飾的目的是方便調用
	static Object B = new Object();
}
class sisuo
{
	public static void main(String [] args){
		new Thread(new ThreadS(true)).start();
		new Thread(new ThreadS(false)).start();
	}
}
//end






---------------------------------------------------------------------------------------------------------------------------------------------
---------- android培訓、 java培訓、期待與您交流!----------
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章