天天看點

黑馬程式員java學習日記——異常和多線程

------- android教育訓練、java教育訓練、期待與您交流! ----------

一、異常處理:

在程式中,錯誤可能産生于程式員沒有預料到的各種情況,或是因為超出了程式員控制之外的環境因素,如使用者的壞資料、試圖打開一個根本不存在的檔案等。在java中這種在程式運作時可能出現的一些錯誤稱為異常。異常是一個在程式執行期間發生的事件,它中斷了正在執行的程式的正常指令流。

異常由來:問題也是現實生活中一個具體的事物,也可以通過java的類的形式進行描述。并封裝成對象。其實就是java對不正常情況進行描述後的對象展現。

對于問題的劃分:兩種:一種是嚴重的問題,一種非嚴重的問題。

對于嚴重的,java通過Error類進行描述。對于Error一般不編寫針對性的代碼對其進行處理。

對于非嚴重的,java通過Exception類進行描述。  對于Exception可以使用針對性的處理方式進行處理。

無論Error或者Exception都具有一些共性内容。比如:不正常情況的資訊,引發原因等。

Throwable

|--Error

|--Exception

2,異常的處理

java 提供了特有的語句進行處理。

try

{

需要被檢測的代碼;

}

catch(異常類 變量)

{

處理異常的代碼;(處理方式)

}

finally

{

一定會執行的語句;

}

3,對捕獲到的異常對象進行常見方法操作。

String getMessage():擷取異常資訊。

class Demo
{
int div(int a,int b)throws Exception//在功能上通過throws的關鍵字聲明了該能
//有可能會出現問題。
	{
		  return a/b;
	}
}
class  ExceptionDemo
{
	public static void main(String[] args) 
	{
		Demo d = new Demo();
		   try
		   {
			   int x = d.div(4,1);
			   System.out.println("x="+x);
		   }
		catch (Exception e)//Exception e = new ArithmeticException();
		{
			System.out.println("除零啦");
			System.out.println(e.getMessage());//  / by zero;
			System.out.println(e.toString());// 異常名稱 : 異常資訊。
			e.printStackTrace();//異常名稱,異常資訊,異常出現的位置。
							//其實jvm預設的異常處理機制,就是在調用
//printStackTrace方法。
							//列印異常的堆棧的跟蹤資訊。
		}		
		System.out.println("over");
	 }
}
           

二、自定義異常:

因為項目中會出現特有的問題,而這些問題并未被java所描述并封裝對象。是以對于這些特有的問題可以按照java的對問題封裝的思想,将特有的問題進行自定義的異常封裝。

當在函數内部出現了throw抛出異常對象,那麼就必須要給對應的處理動作。

要麼在内部try catch處理。

要麼在函數上聲明讓調用者處理。

一般情況下,函數内出現異常,函數上需要聲明。

如何定義異常資訊呢?

因為父類中已經把異常資訊的操作都完成了。是以子類隻要在構造時,将異常資訊傳遞給父類通過super語句。那麼就可以直接通過getMessage方法擷取自定義的異常資訊。

自定義異常:必須是自定義類繼承Exception。

繼承Exception原因:

異常體系有一個特點:因為異常類和異常對象都被抛出。他們都具備可抛性。這個可抛性是Throwable這個體系中獨有特點。隻有這個體系中的類和對象才可以被throws和throw操作。

throws和throw的差別:

throws使用在函數上。後面跟的異常類,可以跟多個,用逗号隔開。

throw使用在函數内。後跟的是異常對象。

class FuShuException extends Exception //getMessage();
{
	private int value;

	FuShuException()
	{
		super();
	}
	FuShuException(String msg,int value)
	{
		super(msg);
		this.value = value;
	}
	public int getValue()
	{
		return value;
	}
}
class Demo
{
	int div(int a,int b)throws FuShuException
	{
		if(b<0)
//手動通過throw關鍵字抛出一個自定義異常對象。
throw new FuShuException("出現了除數是負數的情況------“,b);
		return a/b;
	}
}
class  ExceptionDemo3
{
	public static void main(String[] args) 
	{
		Demo d = new Demo();
		try
		{
			int x = d.div(4,-9);
			System.out.println("x="+x);		
		}
		catch (FuShuException e)
		{
			System.out.println(e.toString());
			//System.out.println("除數出現負數了");
			System.out.println("錯誤的負數是:"+e.getValue());
		}
			System.out.println("over");
	}
}
           

Exceptoin中有一個特殊的子類異常RuntimeException 運作時異常。

如果在函數内容抛出該異常,函數上可以不用聲明,編譯一樣通過。

如果在函數上聲明了該異常。調用者可以不用進行處理。編譯一樣通過;

之是以不用在函數聲明,是因為不需要讓調用者處理。

當該異常發生,希望程式停止。因為在運作時,出現了無法繼續運算的情況,希望停止程式後,對代碼進行修正。

自定義異常時:如果該異常的發生,無法在繼續進行運算,就讓自定義異常繼承RuntimeException。

對于異常分兩種:

1,編譯時被檢測的異常。

2,編譯時不被檢測的異常(運作時異常。RuntimeException以及其子類)

class FuShuException extends RuntimeException
{
	FuShuException(String msg)
	{
		super(msg);
	}
}
class Demo
{
	int div(int a,int b)throws Exception//throws ArithmeticException
	{
		if(b<0)
			throw new Exception("出現了除數為負數了");
		if(b==0)
			throw new ArithmeticException("被零除啦");
		return a/b;
	}
}
class ExceptionDemo4 
{
	public static void main(String[] args) 
	{
				Demo d = new Demo();
			int x = d.div(4,-9);//被除數為負數,出現異常,停止運作
		System.out.println("x="+x);		
				System.out.println("over");
	}
}
           

三、異常部分總結:

是什麼?是對問題的描述。将問題進行對象的封裝。

---------------------------------------------------------------------------------------------------

異常體系:

       Throwable

              |--Error

              |--Exception

                     |--RuntimeException

異常體系的特點:異常體系中的所有類以及建立的對象都具備可抛性。

                            也就是說可以被throw和throws關鍵字所操作。

                            隻有異常體系具備這個特點。

----------------------------------------------------------------------------------------------------

throw和throws的用法:

throw定義在函數内,用于抛出異常對象。

throws定義在函數上,用于抛出異常類,可以抛出多個用逗号隔開。

當函數内容有throw抛出異常對象,并未進行try處理。必須要在函數上聲明,否則編譯失敗。

注意,RuntimeException除外。也就說,函數内如果抛出的RuntimeExcpetion異常,函數上可以不用聲明。

---------------------------------------------------------------------------------------------------------

如果函數聲明了異常,調用者需要進行處理。處理方法可以throws可以try。

異常有兩種:

       編譯時被檢測異常

              該異常在編譯時,如果沒有處理(沒有抛也沒有try),編譯失敗。

              該異常被辨別,代表這可以被處理。

       運作時異常(編譯時不檢測)

              在編譯時,不需要處理,編譯器不檢查。

              該異常的發生,建議不處理,讓程式停止。需要對代碼進行修正。

---------------------------------------------------------------------------------------------------------

異常處理語句:

try

{

       需要被檢測的代碼;

}

catch ()

{

       處理異常的代碼;

}

finally

{

       一定會執行的代碼;

}

有三個結合格式:

1.    try

       {

       }

       catch ()

       {

       }

2.    try

       {

       }

       finally

       {

       }

3.    try

       {

       }

       catch ()

       {

       }

       finally

       {

       }

注意:

1,finally中定義的通常是 關閉資源代碼。因為資源必須釋放。

2,finally隻有一種情況不會執行。當執行到System.exit(0);fianlly不會執行。

3,catch是用于處理異常。如果沒有catch就代表異常沒有被處理過,如果該異常是檢測時異常。那麼必須聲明。

--------------------------------------------------------------------------------------------------------

自定義異常:

       定義類繼承Exception或者RuntimeException

       1,為了讓該自定義類具備可抛性。

       2,讓該類具備操作異常的共性方法。

       當要定義自定義異常的資訊時,可以使用父類已經定義好的功能。

       異常異常資訊傳遞給父類的構造函數。

class MyException extends Exception

       {

              MyException(String message)

              {

                     super(message);

              }

       }

自定義異常:按照java的面向對象思想,将程式中出現的特有問題進行封裝。

-----------------------------------------------------------------------------------------------------

異常的好處:

       1,将問題進行封裝。

       2,将正常流程代碼和問題處理代碼相分離,友善于閱讀。

異常的處理原則:

       1,處理方式有兩種:try 或者 throws。

       2,調用到抛出異常的功能時,抛出幾個,就處理幾個。

              一個try對應多個catch。

       3,多個catch,父類的catch放到最下面。

       4,catch内,需要定義針對性的處理方式。不要簡單的定義printStackTrace,輸出語句。也不要不寫。

              當捕獲到的異常,本功能處理不了時,可以繼續在catch中抛出。

              try

              {

                     throw new AException();

              }

              catch (AException e)

              {

                     throw e;

              }

              如果該異常處理不了,但并不屬于該功能出現的異常。可以将異常轉換後,在抛出和該功能相關的異常。或者異常可以處理,當需要将異常産生的和本功能相關的問題提供出去,讓調用者知道,并處理。也可以将捕獲異常處理後,轉換新的異常。

              try

              {

                     throw new AException();

              }

              catch (AException e)

              {

                     // 對AException處理。

                     throw new BException();

              }

異常的注意事項:

       1,子類在覆寫父類時,如果父類的方法抛出異常,那麼子類的覆寫方法,隻能抛出父類的異常或者該異常的子類。

2,如果父類方法抛出多個異常,那麼子類在覆寫該方法時,隻能抛出父類異常的子集。

3,如果父類或者接口的方法中沒有異常抛出,那麼子類在覆寫方法時,也不可以抛出異常。如果子類方法發生了異常。就必須要進行try處理。絕對不能抛。

四、多線程

如果一次隻完成一件事情,很容易實作,但事實上現實生活中很多事情都是同時進行的,是以在java中為了模拟這種狀态,引入了線程機制。簡單地說,當程式同時完成多件事情時,就是所謂的多線程程式。多線程應用相當廣泛,使用多線程可以建立視窗程式、網絡程式等。

在Windows作業系統是多任務作業系統,它以程序為機關。一個程序是一個包含有自身位址的程式,每個獨立執行的程式都成為程序,也就是正在執行的程式。系統可以配置設定給每個程序一段有限的使用CPU的時間(也可以稱為CPU時間片),CPU在這段時間中執行某個程序,然後下一個時間片又跳至另一個程序中去執行。由于CPU轉換較快,是以使得每個程序好像是同時執行一樣。

建立線程的第一種方式:繼承Thread類

步驟:

1,定義類繼承Thread。

2,複寫Thread類中的run方法。目的:将自定義代碼存儲在run方法。讓線程運作。

3,調用線程的start方法。該方法兩個作用:啟動線程,調用run方法。

為什麼要覆寫run方法呢?

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

例如:建立兩個線程,和主線程交替運作。

class Test extends Thread
{
	//private String name;
	Test(String name)
	{
		//this.name = name;
		super(name);
	}
	public void run()
	{
		for(int x=0; x<60; x++)
		{
			System.out.println(Thread.currentThread().getName()+" run..."+x);// static Thread currentThread():擷取目前線程對象。getName(): 擷取線程名稱。
		}
	}
}
class ThreadTest 
{
	public static void main(String[] args) 
	{
		Test t1 = new Test("one---");
		Test t2 = new Test("two+++");
		t1.start();
		t2.start();
		for(int x=0; x<60; x++)
		{
			System.out.println("main....."+x);
		}
	}
}
           

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

步驟:

1,定義類實作Runnable接口

2,覆寫Runnable接口中的run方法。将線程要運作的代碼存放在該run方法中。

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

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

       為什麼要将Runnable接口的子類對象傳遞給Thread的構造函數。因為,自定義的run方法所屬的對象是Runnable接口的子類對象。是以要讓線程去指定指定對象的run方法。就必須明确該run方法所屬對象。

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

實作Runnable方式的好處是:避免了單繼承的局限性。在定義線程時建議使用實作Runnable方式。

例如:

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

在實作Runnable方式的例子中我們發現了一些問題,通過分析,發現列印出了0,-1,-2等錯票。多線程的運作出現了安全問題。

問題的原因:

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

解決辦法:

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

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

synchronized(對象)

{

       需要被同步的代碼

}

其中對象就如同鎖,持有鎖的線程可以在同步中執行。沒有持有鎖的線程即使擷取cpu的執行權,也進不去,因為沒有擷取鎖。

同步的前提:

1,必須要有兩個或者兩個以上的線程。

2,必須是多個線程使用同一個鎖。保證了同步中隻能有一個線程在運作。

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

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

class Ticket implements Runnable
{
	private  int tick = 1000;
	Object obj = new Object();
	public void run()
	{
		while(true)
		{
			synchronized(obj)
			{
				if(tick>0)
				{
					//try{Thread.sleep(10);}catch(Exception e){}
					System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
				}
			}
		}
	}
}
class  TicketDemo2
{
	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();
	}
}
           

同步函數的話也是可以的,要注意的是,同步函數用的鎖是this。

如果同步函數被靜态修飾後,通過驗證,發現不在是this,因為靜态方法中也不可以定義this。靜态進記憶體時,記憶體中沒有本類對象,但是一定有該類對應的位元組碼檔案對象。

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

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

在同步代碼塊中如何找問題:

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

2,明确共享資料。

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

死鎖:同步中嵌套同步就會造成死鎖。

class Test implements Runnable
{
	private boolean flag;
	Test(boolean flag)
	{
		this.flag = flag;
	}
	public void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(MyLock.locka)//同步,鎖為MyLock.locka
				{
					System.out.println(Thread.currentThread().getName()+"...if locka ");
					synchronized(MyLock.lockb)//同步,鎖為MyLock.lockb
					{
						System.out.println(Thread.currentThread().getName()+"..if lockb");					
					}
				}
			}
		}
		else
		{
			while(true)
			{
				synchronized(MyLock.lockb)//同步,鎖為MyLock.lockb
				{
					System.out.println(Thread.currentThread().getName()+"..else lockb");
					synchronized(MyLock.locka)//同步,鎖為MyLock.locka
					{
						System.out.println(Thread.currentThread().getName()+".....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();
	}
}
單例模式中應用同步代碼塊:
//餓漢式。
/*
class Single
{
	private static final Single s = new Single();
	private Single(){}
	public static Single getInstance()
	{
		return s;
	}
}
*/
//懶漢式(此為考點,要掌握)
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) 
	{
		System.out.println("Hello World!");
	}
}
           

繼續閱讀