1. 程式人生 > >多執行緒 繼承Thread類 實現Runnable介面 多執行緒安全 synchronized 單例設計懶漢 鎖死

多執行緒 繼承Thread類 實現Runnable介面 多執行緒安全 synchronized 單例設計懶漢 鎖死

程序

是一個正在執行中的程式。

每個程序執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元。

舉例子:一個程序,就是一個正在執行的程式。

執行緒
就是程序中的一個獨立的控制單元。

執行緒在控制著程序的執行。

舉例子:例如生產流水線,一個廠房如果只有一條流水線,只能是產品生產完一個再生產一個。如果多裝幾條流水線,就可以同時生產多個,這樣提高了效率,每一條流水線,就可以理解為一個執行緒。

一個程式中至少有一個執行緒。

java vm 啟動的時候會有一個程序java.exe。

該程序中至少有一個執行緒負責java程式的執行。

而且這個執行緒執行的程式碼存在於main方法中。

該執行緒稱之為主執行緒。

擴充套件:其實更細節說明jvm,jvm啟動不止一個執行緒,還有負責垃圾回收機制的執行緒。

Thread

建立執行緒的第一種方式,:繼承Thread類。

步驟:

1,定義類繼承Thread。

2,複寫Thread類中的run方法。

目的:將自定義的程式碼儲存在run方法中,讓執行緒執行

3,呼叫執行緒的start方法

該方法有兩個作用,啟動執行緒,呼叫run方法。

class Demo extends Thread
{
	public void run()
	{
		for (int x=0;x<60 ;x++ )
			System.out.println("demo run"+x);		
	}
}

class  Test
{
	public static void main(String[] args) 
	{
		Demo d = new Demo();   //建立一個物件,就是建立一個執行緒
		d.start();  //開啟執行緒並執行該執行緒的run方法。
		//d.run;    //僅僅是物件呼叫方法,而執行緒建立類,並沒有執行。
		for (int x=0 ; x<60 ;x++ )
			System.out.println("hahah------"+x);
	}
}
發現執行結果每次都不一樣。

因為多個執行緒都在獲取CPU的執行權,cpu執行到誰,誰就執行。

明確一點,在某一時刻,只能有一個程式在執行。(多核除外)

cpu在做著快速的切換,已達到看上去是同時執行的效果。

我們可以形象把多執行緒的執行行為在互相搶奪cpu的執行權。

這就是多執行緒的一個特性:隨機性,誰搶到誰執行,至於執行多長,由cpu說的算。

為什麼要覆蓋run方法呢?

Thread類用於描述執行緒。

該類就定義了一個功能,用於儲存執行緒要執行的程式碼,該儲存功能就是run方法。

也就是說Thread類中的run方法,用於儲存執行緒要執行的程式碼。

/*
聯絡
建立兩個執行緒,和主執行緒交替執行。
*/

class A extends Thread 
{
	private String name;
	A(String name) 
	{
		this.name=name;
	}
	public void run()
	{
		for (int x=0;x<10 ;x++ )
		{
			System.out.println(name+"+"+x);
		}
	}
}


class  Test
{
	public static void main(String[] args) 
	{
		A a1 = new A("one"); 
		A a2 = new A("two"); 
		a1.start();
		a2.start();

		for (int x=0 ; x<10 ;x++ )
			System.out.println("hahah------"+x);
	}
}
多執行緒 狀態
				臨時狀態(阻塞)
		         具備執行資格,但是沒有執行權
					  ^			
					  ^			sleep(time)
					  |	—————————————————>>>
					  |			sleep(時間到)
		start()		  |	<<<—————————————————  			
   被建立——————————>>>         執行			wait()			      凍結	
					  |   —————————————————>>>       沒有執行資格
					  |			notify()
			      stop();    |	<<<—————————————————
		          run方法結束     |
					  V	
					  V	
				        消亡

執行緒都有自己預設的名稱

this.getName() 獲取執行緒名

Thread-編號 ,該編號從0開始。

class A extends Thread 
{
	public void run()
	{
		for (int x=0;x<10 ;x++ )
		{
			System.out.println(this.getName()+"-----"+x); //getName獲取執行緒名
		}
	}
}

class  Test
{
	public static void main(String[] args) 
	{
		A a1 = new A(); 
		A a2 = new A(); 
		a1.start();
		a2.start();

		for (int x=0 ; x<10 ;x++ )
			System.out.println("hahah------"+x);
	}
}

自定義Thread名稱
class A extends Thread 
{
	A(String name) 
	{
		super(name);    // 傳給父類名稱
	}
	public void run()
	{
		for (int x=0;x<10 ;x++ )
		{
			System.out.println(this.getName()+"-----"+x);  //獲取名稱
		}
	}
}

class  Test
{
	public static void main(String[] args) 
	{
		A a1 = new A("ONE"); 
		A a2 = new A("TWO"); 
		a1.start();
		a2.start();

		for (int x=0 ; x<10 ;x++ )
			System.out.println("hahah------"+x);
	}
}

(static Thread)   currentThead(); 獲取當前執行緒物件

getName(); 獲取執行緒名稱。

設定執行緒名成: setName 或者 建構函式(直接 super(name) )。

class A extends Thread 
{
	A(String name) 
	{
		super(name);
	}
	public void run()
	{
		for (int x=0;x<10 ;x++ )
		{
			System.out.println((Thread.currentThread()==this)+"-----"+this.getName()+"----"+x);
		}
	}
}

class  Test
{
	public static void main(String[] args) 
	{
		A a1 = new A("ONE"); 
		A a2 = new A("TWO"); 
		a1.start();
		a2.start();

		for (int x=0 ; x<10 ;x++ )
			System.out.println("hahah------"+x);
	}
}

Runnable介面

建立執行緒的第二種方法 : 實現Runnable介面

為什麼要有Runnable:因為在實際情況中,有可能出現一個類已經是子類,已經繼承了別的類,這時可能需要多執行緒的應用。由於java中單繼承的特性,所以設計出Runnable介面,用於解決繼承類父類的情況下,實現多執行緒的情況

步驟:

1,定義類實現Runnable介面。

2,覆蓋Runnable介面中的run方法。

將執行緒要執行的程式碼放在該run方法中。

3,通過Thread類建立執行緒物件。

4,將Runnalbe介面的子類物件作為實際引數傳遞給Thread類的建構函式.

為什麼要將Runnable介面的子類物件傳遞給Thread的建構函式。

因為,自定義的run方法所屬的物件是Runnable介面的子類物件。

所以要讓執行緒去執行指定物件的run方法。就必須明確該run方法的所屬物件。

5,呼叫Thread類的start方法開啟執行緒並呼叫Runnable介面子類中的run方法。

/*
 需求:簡單的賣票程式。
 多個視窗買票。
*/
class Ticket implements Runnable 
{
	private int tick = 100;
	public void run()
	{
		while(true)
		{	
			if(tick>0)
				System.out.println(Thread.currentThread().getName()+"---sale:"+tick--);
		}
	}
}
class  Test
{
	public static void main(String[] args) 
	{
		Ticket t= new Ticket();
		
		Thread t1= new Thread(t);
		Thread t2= new Thread(t);
		Thread t3= new Thread(t);
		Thread t4= new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

Runnable實現方式和Thread繼承方式的區別

繼承Thread:只能單繼承,執行緒程式碼存放在Thread子類的run方法中。

實現Runnable:可以避免單繼承的侷限性,執行緒程式碼存放在Runnable介面的子類的run方法中。

多執行緒的安全問題

問題原因:

當多條語句在操作同一個執行緒共享資料時,一個執行緒對多條語句執行了一部分,沒有執行完。

另一個執行緒參與進來執行,導致共享資料的錯誤。

解決辦法:

對多條操作共享資料的語句,只能讓一個執行緒執行完,在執行過程中,其他執行緒不可以參與執行。 

java對於多執行緒的安全問題提供類專業的解決方式。

就是同步程式碼塊。

synchronized (物件)

{

需要被同步的程式碼;

}

物件如同鎖,持有鎖的執行緒可以在同步中執行。

沒有持有鎖的執行緒即使獲取cpu的執行權,也進不去,因為沒有獲取門鎖。

如果火車上的衛生間。

同步的前提

1,必須要有兩個或者兩個以上的執行緒。

2,必須是多個執行緒使用同一個鎖。

必須保證同步中只有一個執行緒在執行。

好處:解決了多執行緒的安全問題。

弊端:多個執行緒都需要判斷鎖,較為消耗資源。

class Ticket implements Runnable
{
	private  int num = 100;

	Object obj = new Object();
	public void run()
	{
		while(true)
		{
			synchronized(obj)
			{
				if(num>0)
				{
					try{Thread.sleep(10);}catch (InterruptedException e){}
					
					System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
				}
			}
		}
	}
}


class  TicketDemo
{
	public static void main(String[] args) 
	{

		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);

		t1.start();
		t2.start();
		t3.start();
		t4.start();

如何找安全問題:

1,明確哪些程式碼是多執行緒執行程式碼。

2,明確共享資料。

3,明確多執行緒執行程式碼中哪些語句是操作共享資料的。

class Bank
{
	private int sum;
	public synchronized void add(int n)
	{
			sum+=n;
			try
			{
				Thread.sleep(10);
			}
			catch (Exception e)
			{
			}
			System.out.println("sum="+sum);
	}
}

class Cus implements Runnable
{
	private Bank b = new Bank();
	public void run()
	{
		for(int x=0;x<3;x++)
			b.add(100);
	}
}

class Test
{
	public static void main (String[] args)
	{
		Cus s= new Cus();
		Thread s1= new Thread(s);
		Thread s2= new Thread(s);
		s1.start();
		s2.start();
	}
}

同步程式碼塊
class Bank
{
	private int sum;
	Object obj = new Object();
	public void add(int n)
	{
		synchronized()
		{
			sum+=n;
			try
			{
				Thread.sleep(10);
			}
			catch (Exception e)
			{
			}
			System.out.println("sum="+sum);
		}
	}
}

同步函式
class Bank
{
	private int sum;
	Object obj = new Object();
	public void synchronized add(int n)
	{
			sum+=n;
			try
			{
				Thread.sleep(10);
			}
			catch (Exception e)
			{
			}
			System.out.println("sum="+sum);
	}
}

同步函式用的是哪一個鎖

函式需要被物件呼叫,那麼函式都有一個所屬物件引用,就是this。

所以同步函式使用的鎖是this。

驗證

一個執行緒在同步程式碼塊中,

一個執行緒在同步函式中。

都在執行買票動作。

class Ticket implements Runnable
{
	private  int tick = 100;
	boolean flag = true;
	public void run()
	{
		if (flag)
		{
			while(true)
			{
				synchronized (this)
				{
					if(tick>0)
					{
					try{Thread.sleep(10);}catch (InterruptedException e){}	
					System.out.println(Thread.currentThread().getName()+".....code...."+tick--);
					}
				}
			}
		}
		else
			while(true)
				show();
		}
	public synchronized void show()//this
	{
		if(tick>0)
			{
				try{Thread.sleep(10);}catch (InterruptedException e){}	
				System.out.println(Thread.currentThread().getName()+".....show...."+tick--);
			}
	}
}


class  Test
{
	public static void main(String[] args) 
	{

		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);

                t1.start();
                try{Thread.sleep(10);}catch (Exception e){}
                
		t.flag = false;
		t2.start();

	}
}

static修飾同步函式

如果同步函式被靜態修飾後,使用的鎖匙class物件。

驗證發現,不是this,因為靜態方法中不可以定義this。

靜態進記憶體時,記憶體中沒有本類物件,但是一定有該類對應的位元組碼檔案物件。

類名.class 。 該物件的型別是calss。

靜態的同步方法,使用的鎖是 該方法所在類的位元組碼檔案物件。  類名.class 

class Ticket implements Runnable
{
	private static  int tick = 100;
	boolean flag = true;
	public void run()
	{
		if (flag)
		{
			while(true)
			{
				synchronized (Ticket.class)	// 鎖為 類名.class
				{
					if(tick>0)
					{
					try{Thread.sleep(10);}catch (InterruptedException e){}	
					System.out.println(Thread.currentThread().getName()+".....code...."+tick--);
					}
				}
			}
		}
		else
			while(true)
				show();
		}
	public static synchronized void show()		// static修飾 synchronized
	{
		if(tick>0)
			{
				try{Thread.sleep(10);}catch (InterruptedException e){}	
				System.out.println(Thread.currentThread().getName()+".....show...."+tick--);
			}
	}
}

單例設計模式--懶漢式
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;
	}
}
class SingleDemo
{
	public static void main(String [] args)
	{
		Single s1=Single.getInstance();
		Single s2=Single.getInstance();
			
		System.out.println(s1==s2);
	}
}

死鎖

同步中巢狀同步。

class Test implements Runnable
{
	private boolean flag;
	Test(boolean flag)
	{
		this.flag = flag;
	}
	
	public void run()
	{
		if (flag)
		{
			while(true)
			{
				synchronized (MyLock.Locka)
				{
					System.out.println("1");
					synchronized (MyLock.Lockb)
					{
						System.out.println("2");
					}
				}
			}	
		}
		else
		{
			while(true)
			{
				synchronized (MyLock.Lockb)
				{
					System.out.println("3");
					synchronized (MyLock.Locka)
					{
						System.out.println("4");
					}
				}
			}	
		}

	}
}

class MyLock
{
	static Object Locka = new Object();
	static Object Lockb = new Object();
}

class  DeadlockTect
{
	public static void main(String[] args) 
	{
		Thread t1 = new Thread(new Test(true));
		Thread t2 = new Thread(new Test(false));
		t1.start();
		t2.start();

	}
}