天天看點

多線程、單例設計模式、死鎖2  單例設計模式3  死鎖

1  多線程

1.1  程序:

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

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

1.2  線程:

   線程就是程序中的一個獨立的控制單元。

      線程在控制着程序的執行,一個程序中至少有一個線程。

1.3  主線程:

     Java虛拟機(JVM)啟動的時候會有一個程序java.exe,該程序中至少有一個線程負責java程式的執行,而且這個線程運作的代碼存在于main方法中,該線程稱之為主線程。

其實JVM啟動不止一個線程,還有負責垃圾回收機制的線程。

1.4  建立線程:

通過對API的查找,Java已經提供了對線程這類事物的描述,就是Thread類。

建立線程的第一種方式:繼承Thread類,覆run()方法。

步驟:1,自定義類,并繼承Thread類。

          2、重寫Thread類中的run()方法。

                目的:将定義的代碼存儲在run()方法中,讓線程運作。

          3、調用線程的start()方法。

               start()方法有兩個作用:啟動線程,調用run()方法。

代碼示例如下:

class Demo extends Thread {  //要建立線程必須繼承Thread類
	public void run(){
		for(int x=0;x<120;x++)
			System.out.println("Demo run--"+x);
	}
}
class ThreadDemo{
	public static void main(String[] args){
		Demo d = new Demo();  //建立好一個線程
		d.start();   //啟動線程,并調用run()方法。
		//d.run();   //這樣,僅僅是對象調用方法,而線程建立了,并沒有運作。
		
		for(int x=0;x<120;x++)
			System.out.println("Hello World--"+x);
	}
  }
           

發現運作結果每一次都不同。

因為多個線程都擷取CPU執行權,CPU執行到誰,誰就運作。Demo線程和主線程争奪CPU。

明确一點,在某一個時刻,隻能有一個程式在運作(多核除外)。

CPU在做着快速切換,以達到看上去是同時運作的效果。

我們可以形象的把多線程的運作形容為在互相搶奪CPU的執行權(CPU資源);

這就是多線程的一個特性:随機性。誰搶到誰執行,至于執行多長時間,CPU說的算。

為什麼要覆寫run()方法呢?

Thread類用于描述線程;該類就定義了一個功能,用于存儲線程要運作的代碼,該存儲功能就是run()方法。也就是說Thread類中的run()方法,用于存儲線程要執行的代碼。

線程都有自己預設的名稱:

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

currentThread();  該方法擷取目前線程對象;

getName();  該方法擷取線程名稱

設定線程名稱:serName()或者構造函數。

1.5  簡單的賣票程式:

需求:簡單的賣票程式,多個視窗同時賣票。

建立線程的第二種方式:實作Runnable接口。

步驟:1、定義類實作Runnable接口。

          2、覆寫Runnable接口中的run()方法。

               将線程要運作的代碼存放在該run()方法中。

          3、通過Thread類建立線程對象。

          4、将Runnable接口的子類對象作為實際參數,傳遞給Thread類的構造函數。

               為什麼要将Runnable接口的子類對象傳遞給Thread的構造函數:

               因為,自定義的run()方法所屬的對象是Runnable接口的子類對象。

               是以要讓線程去執行指定對象的run()方法,就必須明确該run()方法所屬的對象。

          5、調用Thread類的start()方法,啟動線程,并調用Runnable接口子類的run方法。

第一種和第二種,即實作Runnable接口方式和繼承Thread類方式有什麼差別?

實作方式的好處:避免了單繼承的局限性。

在定義線程時,建議使用實作方式。

兩種方式的差別:

繼承Thread類:線程代碼存放在Thread子類的run()方法中。

實作Runnable接口:線程代碼存放在接口的子類的run()方法中。

多線程的安全問題:

問題的原因:當多條語句在操作同一個線程共享資料時,一個線程對多條語句隻執行了一部分,還沒有執行完,這時另一個線程參與進來執行,導緻了共享資料的錯誤。

解決辦法:對多條操作共享資料的語句,隻能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。

Java對于多線程的安全問題提供了專業的解決方式:同步代碼塊和同步函數。

同步代碼塊:

synchronized(對象)

{

需要被同步的代碼; //即操作共享資料的代碼,是需要被同步的代碼

}

    對象如同鎖,持有鎖的線程可以在同步中執行。

沒有持有鎖的線程即使擷取CPU的執行權,也進不去,因為沒有擷取鎖。

同步的前提:

1、必須要有兩個或兩個以上的線程。鎖住操作共享資料的代碼。

2、必須是多個線程使用同一個鎖(即同一個對象),必須保證同步中隻能有一個線程在運作。

好處:解決了多線程的安全問題

弊端:多個線程都需要判斷鎖,較為消耗資源。

賣票程式代碼:

class Ticket implements Runnable {//extends Thread {
	private int tick = 100;
	Object obj = new Object(); //解決安全問題
	public void run() {
		while(true) {
			synchronized(obj){ //同步代碼塊,解決安全問題
				if(tick>0) {
					try{Thread.sleep(10);}catch(Exception e){} //sleep()抛出一個異常。出現-1、-2,出現安全問題
					System.out.println(Thread.currentThread().getName()+"sale: "+tick--);
				}
			}
		}
	}
}
class ThreadDemo2 {
	public static void main(String[] args){
		Ticket t = new Ticket();  //t不是線程,因為與Thread類無關
		
		Thread t1 = new Thread(t); //建立一個線程,并把Runnable子類對象傳遞給Thread構造函數
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		System.out.println("---main---");
	}
  }
           

1.6  同步函數:

把synchronized作為修飾符放在函數聲明中,此函數就具有同步功能(相當于同步代碼塊)。

這個被synchronized修飾的函數就是同步函數。

同步函數用的哪一個鎖呢?

函數需要被對象調用,那麼函數都有一個所屬對象的引用,就是this。

是以同步函數使用的同步鎖是this。

牢記同步的兩個前提,如果加同步後還有問題,就檢視是否滿足同步到前提。

靜态同步函數:

如果同步函數被靜态修飾後,使用的鎖是什麼呢?

通過驗證,發現不再是this,因為靜态方法中也不可以定義this。

靜态進記憶體時,記憶體中沒有本類對象,但是一定有該類對應的位元組碼檔案對象。getClass()方法

類名.class  該對象的類型是class

靜态的同步方法,使用的同步鎖是該方法所在類的位元組碼檔案對象:類名.class

需求:銀行有一個金庫,有兩個儲戶分别存300元,每次存100,存三次。

目的:該程式是否有安全問題,如果有,如何解決?

如何找問題(需要同步的代碼怎麼找):

1、明确哪些代碼是多線程運作代碼。

2、明确共享資料。

3、明确多線程運作代碼中哪些語句是操作共享資料的。

代碼:

class Bank {
	private int sum;
	//Object obj = new Object();
	public synchronized void add(int n){  //用synchronized修飾,add為同步函數
		//synchronized(obj){
			sum = 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 ThreadDemo3 {
	public static void main(String[] args) {
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
  }
           

2  單例設計模式

設計模式:解決某一類問題最行之有效的方法。

單例設計模式:一個類在記憶體隻存在一個對象,想要保證對象唯一。

1、為了避免其他程式過多建立該類對象,先禁止其他程式建立該類對象

2、還為了讓其他程式可以通路到該類對象,隻好在本類中,自定義一個對象。

3、為了友善其他程式對自定義對象的通路,可以對外提供一些通路方式。

這三部分怎麼用代碼展現呢?

1、将構造函數私有化

2、在類中建立一個本類對象

3、提供一個方法可以擷取到該對象。

對于事物該怎麼描述,還怎麼描述。

當需要将該事物的對象保證在記憶體中唯一時,就将以上的三步加上即可。

2.1  餓漢式 :

    餓漢式:先初始化對象。

Single類一進記憶體,就已經建立好了對象。

class Single {

      private Single(){}

      private static final Single s = new Single();

      public static Single getInstance() {

              return s;

      }

}

2.2  懶漢式

懶漢式:對象是方法被調用時,才初始化,也叫做對象的延時加載。

Single類進記憶體,對象還沒有存在,隻有調用了getInstance()方法時,才建立對象。

class Single {

      private Single(){}

      private static Single s = null;

      public static Single getInstance() {

            if(s==null)

                s = new Single();

           return s;

      }

}

代碼示例:

class Single {          //懶漢式延遲加載,會出現多線程安全問題,用同步鎖解決
	private Single(){}
	private static Single s = null;
	
	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){
		System.out.println(Single.getInstance().getClass());
	}
}
           

3  死鎖

死鎖:同步中嵌套同步,會發生死鎖。

比如,A鎖的同步代碼中需要B鎖,而B鎖的同步代碼中需要A鎖,

 就會産生沖突,發生死鎖。

代碼示例:

class Ticket implements Runnable {
	private int tick = 1000;
	Object obj = new Object();
	boolean flag = true;
	
	public void run() {
		if(flag) {
			while(true) {
				synchronized(obj) {  //obj鎖
					show();          //需要this鎖,而此時this鎖被使用
				}                    //同步中嵌套同步,出現死鎖
			}
		}
		else
			while(true)
				show();
	}
	public synchronized void show() {  //this鎖
		synchronized(obj) {            //需要obj鎖,而此時obj鎖被使用
			if(tick > 0) {
				try{Thread.sleep(10);}catch(Exception e){}
				System.out.println(Thread.currentThread().getName()+"....code: "+tick--);
			}
		}
	}
}
class DeadLockDemo {
	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();
	}
}
           

小練習:寫一個死鎖程式。

提示:多線程,同步鎖,死鎖。

class Test implements Runnable {
	private boolean flag;
	Test(boolean flag){
		this.flag = flag;
	}
	public void run() {
		if(flag){
			synchronized(MyLock.locka){    //使用a鎖
				System.out.println("if locka");
				synchronized(MyLock.lockb){  //需要b鎖,而此時b鎖在被使用
					System.out.println("if lockb");
				}
			}
		}
		else{
			synchronized(MyLock.lockb){    //使用b鎖
				System.out.println("else lockb");
				synchronized(MyLock.locka){   //需要a鎖,而此時a鎖在被使用
					System.out.println("else locka");
				}
			}
		}
	}
}
class MyLock {
	static Object locka = new Object();
	static Object lockb = new Object();
}
class DeadLockTest{
	public static void main(String[] args){
		Thread t1 = new Thread(new Test(true));
		Thread t2 = new Thread(new Test(false));
		t1.start();
		t2.start();
	}
}
           

轉載于:https://www.cnblogs.com/rockray/p/3612016.html