天天看點

黑馬程式員Java學習日記(3)異常,String,多線程

------- <a href="http://www.itheima.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow"  target="blank">android教育訓練</a>、<a href="http://www.itheima.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow"  target="blank">java教育訓練</a>、期待與您交流! ----------

1.異常處理機制:

(1)概念:就是程式在運作時出現的不正常情況。     

(2)error與Exception的差別:

Error:對于嚴重的問題,Java通過Error類進行描述。一般不編寫針對性的代碼

      對其進行處理。

Exception:對于非嚴重的問題,Java通過Exception來進行描述,可以使用針對

          性的方法去處理。

結論:無論Error還是Exception都有一些共性的内容比如:不正常的内容,引發原因,有共同的父類Throwable。

(3)異常的處理:

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

   try

   {

    需要檢測的代碼;

   }

   catch(異常類 變量)

   {

    處理異常的代碼;

    }

   finally

   {

     一定會執行的語句;

   }

(4)對捕獲到的異常處理的方法:

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

  String toString();  異常名稱,異常資訊,以字元串形式傳回。

  printStackTrace()異常名稱,異常資訊,異常出現的位置 jvm預設的異常處理機制。

   1、聲明異常時,建議聲明更為具體的異常,這樣處理的更為具體。

   2、對方聲明幾個異常,就對應幾個catch代碼塊如果多個異常的出現繼承關系,父類異常catch放在最下面自定義異

     常。

(5)自定義異常:

   1、項目中會出現特有的問題,而這些問題别沒有被java所描述并封裝成對象

   2、自定義異常必須手動抛出 用throw關鍵字。

   3、當在函數内部出現了throw抛出異常,必須給出相應的處理動作要麼在函

         數内部try catch 要麼在函數上聲明讓調用者處理 RuntimeException除外。 

   4、一般,函數内部出現異常,要在函數上聲明。

   5、自定義異常必須是自定義類繼承Exception,因為異常體系有一個特點,異

         常類和異常對象都具有可抛性,而可抛性是throwable體系特有的特點。

   6、那麼該如何定義異常資訊呢?因為父類已經把異常資訊的操作都完成了,

         是以子類在構造函數時,通過super語句将資訊傳遞給父類,就可以直接

         通過getMessage獲得自定義的異常資訊。

代碼例子:

需求:在這個運作程式中,除數不能是負數。

代碼例子:

  class  FuShuException extends Exception

  {

       FuShuException(String msg)

       {

           super(msg);

        }

  }

  class Demo

  {

       int div(int a,int b)throws FuShuException

      {

          if(b<0)

          throw new FuShuException("出現了商為負數的情況-------by FuSu");

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

           }

           catch (FuShuException e)

           {

             System.out.println(e.toString());   

          }

     }

}

(6)throws與throw的差別:

             throws用在函數上

             throw用在函數中

             throws後面跟的是異常類,可以跟多個,用逗号分開

             throw後面跟的是異常對象,。

(7)運作時異常RuntimeException

     1、在函數内出現了這個異常,函數上可以不去聲明,編譯一樣可以通過,如果在函數上聲明了這個異常,調用者

          可以不用處理,編譯也可以通過。

     2、運作時異常之是以可以不用在函數上聲明,是因為不需要讓調用者處理,當該異常發生,希望程式停止,對代

          碼進行修改。

     3、在自定義異常時,如果這個異常的發生,無法再進行運算,可以讓着個異常繼承運作時異常。

     4、異常分為兩種:

          4.1、編譯時被檢測的異常,在函數上标示出,讓調用者去處理,問題可以處理,不需要修改代碼。    

          4.2、編譯時不被檢測的異常(運作時異常),必須停下程式,修改代碼。

 (8)finally語句: 

      一定要執行的語句,比如資料庫操作:斷開連接配接 ,通常用于關閉資源。

 (9)異常處理的格式:

     1、try{}  catch(){}

     2、try{}  catch(){}  finally{}

     3、try{}  finally{}

代碼例子:

try{}  catch(){} 語句:

   class Demo

   {

       //不需要聲明因為内部已經解決

       public void method()        

       {

            try

            {

                thow new Exception();

            }

            catch(Excetion e)

            {

            }

        }

 }

try{}  finally{} 語句:

 class Demo

 { 

      public void method()

      {

          try

          {

             thow new Exception();

          }

          finally

          {

          }

      }

}

沒有catch,代表異常沒有被處理,如果這個異常是編譯時異常,那麼這個異常必須聲明。

注意:try catch中的return語句要先檢查finally是否在,然後才能傳回。

(10)異常在子父類中的展現:

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

            類。

      2、如果父類抛出多個異常,那麼子類隻能抛出父類抛出異常的子集。

      3、如果父類或者接口沒有抛出異常,那麼子類在覆寫方法時也不能抛出異常如果子類發生了異常,隻能try 不能

            抛出。

2.String:

(1)String特點:

      1、字元串是一個特殊的對象。 

      2、字元串一旦初始化就不可以被改變。

      3、String不能被繼承。

      4、String str = “abc”與 String str1 = new String(“abc”);的差別:

             “abc”是一個對象,前者隻建立了一個對象,後者為兩個對象。

(2)String裡面的常用方法:

     1、擷取的方法:

  //字元串中的包含的字元數,也就是字元串的長度。

  int length();             

  //根據指定位置擷取位置上的某個字元。

  char charAt(int index);

  //傳回的是ch第一次在字元串中出現的位置。                       

  int indexOf(int ch);       

  //傳回的是從fromIndex指定位置開始,查找 ch在在字元串中出現的位置。

  int indexOf(int ch,int fromIndex);  

  //傳回指定子字元串在此字元串中第一次出現處的索引。

  int indexOf(String str);  

  //從fromIndex指定位置開始,擷取子字元串在字元串中出現的位置。

  int indexOf(String str,int fromIndex);  

  //傳回的是指定子字元串在此字元串中最右邊出現的位置。

  int lastIndexOf(String str); 

   //從指定位置開始擷取該字元串中的一部分,包含結尾。

String substring(int index);  

String substring(int index,int fromIndex);    

2、判斷

 boolean contains(str);          //字元串中是否包含某一個子串。

 boolean isEmpty();               //字元串中是否有内容。 

 boolean startsWith(str);       //字元串是否是以指定内容開頭。

 boolean endsWith(str);        //字元串是否是以指定内容結尾。

 boolean equalsIgnoreCase();     //判斷内容是否相同,并忽略大小寫。

 boolean  equals("String str")        //判斷字元串内容是否相同

3、轉換

将字元數組轉換成字元串:

構造函數:

          String(char[]),           // 将整個字元數組轉成字元串

          String(char[],offset,count); // 将數組中的一部分轉成字元串

靜态方法:

     static String copyValueOf(char[]);  //将整個字元數組轉成字元串。

     static String copyValueOf(char[],offset,count);//将數組中的一部分轉成字元串    

       static String valueOf(char[]);    

将字元串轉換成字元數組:

  char[]  toCharArray();    

将位元組數組轉換成字元串:

   byte[]  getBytes();    将字元串轉換成位元組數組。

4、替換:

replace(oldchar,newchar);

傳回的是一個新的字元串。如果要替換的字元不存在,傳回的還是原字元串。

replace(str,restr);

5、切割

String[] split(regex);

6、子串

String substring(begin);

String substring(begin,end);

7、轉換大小寫,去除空格,比較

7.1、将字元串轉成大寫或小寫。

String toUpperCase();

String toLowerCase();

7.2、将字元串兩端的多個空格去除。

String trim();

7.3、對兩個字元串進行自然順序的比較。

int compareTo(String);

(3)練習1      

模拟一個trim方法,去除字元串兩端的空格。

思路:

1、判斷字元串第一個位置是否是空格,如果是,就繼續向後判斷,直到不

       是空格為止,結尾處判斷空格也是一樣。

2、當開始和結尾都判斷到不是空格時,就是要擷取的字元串。

3、必須保證開始的角标小于等于結尾的角标,否則沒有意義。

代碼例子:

public static void main(String[] args) 

{

      String s1 ="    zhang  hao     ";   

     int start=0,end =s1.length()-1; 

    while(start<=end && s1.charAt(start)==' ')

             start++;     

   while(start<=end && s1.charAt(end)==' ')

           end--; 

    //因為包含頭不包含尾,是以結 尾處需要加1。

    System.out.println(s1.substring(start,end+1));  

 }

(4)練習2   

    将一個字元串進行整體反轉。

思路:

    1、将字元串變成字元數組

    2、将數組進行反轉

    3、将數組變成字元串

    4、隻要将反轉的部分的開始和結束位置作為參數傳遞即可。

代碼例子:

 class StringDemo1 

 {

     public static void main(String[] args) 

     {

         String s = "zhanghao haha";

         System.out.println(reverseString(s));

     }

     public static String reverseString(String s)

     {

         char[] arr =s.toCharArray();   //字元串變字元數組

         reverse(arr);                         //将數組進行反轉

         return new String(arr);         //将數組變成字元串   

      }

     private static void reverse(char[] arr)

     {

        for(int start=0, end=arr.length-1; start<end; start++,end--)

        {

           swap(arr,start,end);                  

         }           

     private static void swap(char[] arr,int x,int y)

    {

         char temp =arr[x];

         arr[x]=arr[y];

         arr[y]=temp;

      }

}                           

 将字元串中的指定部分進行反轉。

代碼例子:

class StringDemo2

{

     public static void main(String[] args) 

     {

        String s = "zhanghao haha";

        System.out.println(reverseString(s,2,7));

     }

       public static String reverseString(String s,int start,int end)

    {

          char[] arr =s.toCharArray();

          reverse(arr,start,end);

          return new String(arr);

         //System.out.println(new String(arr));

     }       

     private static void reverse(char[] arr,int x,int y)

     {

        for(int start=x, end=y-1; start<end; start++,end--)

            swap(arr,start,end);

       }

       private static void swap(char[] arr,int x,int y)

      {

            char temp =arr[x];

            arr[x]=arr[y];

            arr[y]=temp;      

      }  

}

(5)練習3:

   擷取一個字元串在另一個字元串中出現的次數。

       “abkkcdkkefkkskk”

思路:

     1、定義一個計數器。

     2、擷取KK第一次出現的位置。

     3、從第一次出現的位置後,剩餘的字元串中繼續擷取kk出現的位置,每擷取一就計數一次。   

     4、當擷取不到時,計數完成。

代碼例子:

  //第一種方式,比較繁瑣,用的是indexOf(String x)和substring()。

   class StringDemo1 

   {

       public static void main(String[] args) 

       {

           String str ="abkkcdkkefkkskk";

           System.out.println(getSubCount_2(str,"kk"));

        }

       public static int getSubCount_2(String str,String key)

      {

               int count =0;

               int index =0;

            while((index=str.indexOf(key))!=-1)

            {  

                str=str.substring(index+key.length());

                     count++;

             }

                  return count;

        } 

}

 //第二種方式,比較簡單,用的是indexOf(String x,int y);

 class StringDemo2 

 {

     public static void main(String[] args) 

     {

        String str ="abkkcdkkefkkskk";

        System.out.println(getSubCount(str,"kk"));

     } 

     public static int getSubCount(String str,String key)

     {

               int count =0;

               int index =0;

           while((index=str.indexOf(key,index))!=-1)

              System.out.println("index"+index);

              index=index+key.length();

               count++;

      }

   return count;

 }

(6)練習4: 

     擷取倆個字元串中最大相同的子串。

思路:

    1、将短的那個字串按照長度遞減的方式擷取到。

    2、将每次擷取到的子串去長度中判斷是否包含,如果包含,說明已經找到了。

代碼例子:

class StringDemo2 

{

    public static void main(String[] args) 

    {

           String s1 ="abcwerthelloyuiodef";

           String s2 ="cvhellobnm";

           System.out.println(getSubString(s1,s2));

     }

     public static String getSubString(String s1,String s2)

     {

           for(int x =0; x<s2.length(); x++)

                {

               for(int y=0,z=s2.length()-x; z!= s2.length()+1; y++,z++)

               {

                       String temp = s2.substring(y,z);

                      if(s1.contains(temp))

                          return temp;

                }  

            } 

           return "";

         }

   }

3.StingBuffer

(1)StingBuffer的特點及作用:

字元串的組成原理就是通過該類實作的。

StingBuffer這個類被final修飾,不能被繼承。

StingBuffer是線程同步的。

StingBuffer可以對字元串内容進行增删。

StingBuffer是一個容器。

很多方法與StingBuffer相同。

StingBuffer是可變長度的。

可以直接操作多個資料類型。

一次最終會通過toString方法變成字元串。

(2)StingBuffer裡面的常用方法:

 1、存儲:StingBuffer append(); 

 2、删除:StingBuffer delete(int start,int end);

         StingBuffer deleteCharAt(int index);

 3、擷取:char charAt(int index);

          int indexOf(String str);

          int lastIndexOf(String str);

 4、修改:StingBuffer replace(int start, int end, String str);

 5、反轉:StingBuffer reverse();

(3)什麼時候使用緩沖區:

   當資料類型不确定,資料的個數不确定,而且最後變成字元串的時候。

4.StingBuilder

(1)StringBuilder特點與作用:

一個可變的字元序列,此類提供一個與StingBuffer相容的API,但不保證同步。

該類被設計用作StingBuffer類的一個簡易的替換,用在字元串緩沖區被單個線程使用個時候,它比StingBuffer的效率要快。

(2)StingBuffer與StringBuilder的差別:

 StingBuffer是線程同步的,安全的,需要判斷鎖,效率慢。

 StringBuilder是線程不同步的,不安全,不需要判斷鎖,效率快。

5.正規表達式:

(1)概念:符合一定規則的表達式。

   1、特點:用一些特定的符号來表示一些代碼的操作。

   2、作用:專門用于操作字元串。

   3、好處:可以簡化對字元串的複雜操作。

   4、弊端:符号定義越多,正則越長,閱讀性越差。

(2)正規表達式的構造摘要:

常用的:

 1、字元類 

[abc]                a、b 或 c(簡單類) 

[^abc]               任何字元,除了 a、b 或 c(否定) 

[a-zA-Z]              a 到 z 或 A 到 Z,兩頭的字母包括在内(範圍) 

[a-d[m-p]]            a 到 d 或 m 到 p:[a-dm-p](并集) 

[a-z&&[def]]          d、e 或 f(交集) 

[a-z&&[^bc]]          a 到 z,除了 b 和 c:[ad-z](減去) 

[a-z&&[^m-p]]         a 到 z,而非 m 到 p:[a-lq-z](減去) 

2、預定義字元類:

 .   任何字元(與行結束符可能比對也可能不比對) 

\d    數字:[0-9] 

\D   非數字: [^0-9] 

\s    空白字元:[ \t\n\x0B\f\r] 

\S   非空白字元:[^\s] 

\w   單詞字元:[a-zA-Z_0-9] 

\W 非單詞字元:[^\w] 

3、邊界比對器 :

^     行的開頭 

$     行的結尾 

\b    單詞邊界 

\B    非單詞邊界 

\A   輸入的開頭 

\G 上一個比對的結尾 

\Z   輸入的結尾,僅用于最後的結束符(如果有的話) 

\z    輸入的結尾 

4、Greedy 數量詞:

X?          X,一次或一次也沒有 

X*         X,零次或多次 

X+         X,一次或多次 

X{n}      X,恰好 n 次 

X{n,}     X,至少 n 次 

X{n,m}   X,至少 n 次,但是不超過 m 次

(3)具體的操作功能:

   1、比對: 用的是String中的matches方法。

             用規則比對整個字元串,隻有有一處不符合規則,就比對結束,

             傳回false,傳回的是真假。

   2、切割:用的是String 中的split方法。

            傳回的是規則以外的字元串數組。

   3、替換:用的是String中的replaceAll方法。

            傳回的是一個替換後的字元串,replaceAll裡面需要傳兩個參數,

            一個替換的規則,一個就是要替換的字元串。

   4、擷取:将字元串中符合規則的子串取出。

        步驟:

     1、将正規表達式封裝成對象。

     2、讓正則對象和要操作的字元串想關聯。

     3、關聯後擷取正則比對引擎。

     4、通過引擎對符合規則的字元串進行操作,比如取出。

 代碼例子:

import java.util.regex.*;

class RegexDemo 

{

   public static void main(String[] args) 

   {

        getDemo();

   }

   public static void getDemo()

   {

       String str = "ming tian jiu yao fang jia le ,da jia。";

       System.out.println(str);

       String reg = "\\b[a-z]{4}\\b";

        //将規則封裝成對象。

        Pattern p = Pattern.compile(reg);

       //讓正則對象和要作用的字元串相關聯。擷取比對器對象。

        Matcher m  = p.matcher(str);

        //System.out.println(m.matches());

        //System.out.println("matches:"+m.matches());

          while(m.find())

          {

            System.out.println(m.group());

            System.out.println(m.start()+"...."+m.end());

          }

      }

}

6.多線程:

(1)線程和程序概念:

 1、程序:正在進行中的程式。

 2、線程:程序中的多條執行路徑。

(2)程序與線程的關系:

 1、線程控制着程序的執行進度。

 2、一個程序中至少有一個線程,就是主函數。

(3)多線程的特點:

 随機性,每個線程都擷取CPU的執行權,到底誰執行,執行多久由CPU來決定。多個線程之間交 替執行。

(4)啟動線程:

 啟動線程的唯一方法是:線程類對象.start();

 用于開啟線程并執行線程中的run();

 線程類的run()方法,用于封裝要運作的代碼。

(5)建立的線程的兩種方式:

 1、定義線程的第一種方法:

     步驟:

     1.1、定義一個類繼承Thread類。

     1.2、複寫Thread類中的run方法,用于将自定義代碼存儲到run方法中,

          讓線程運作。

     1.3、調用線程的start方法來啟動線程。

代碼例子:

class Demo1 extends Thread 

{

     //覆寫run方法。

     public void run()

     {

        System.out.println("demo1 run");

       }

}

class ThreadDemo

{

    public static void main(String[] args) 

    {

        //定義子類對象。

         Demo1 d=new Demo1();

        //啟動線程。

        t.start();

     }

}

2、定義線程的第二種方法:

2.1、定義一個類實作Runnable接口。

2.2、覆寫Runnable中的run方法。

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

2.4、将Runnable接口的子類對象作為實際參數傳遞到Thread類的構造函數中。

2.5、調用Thread類的start方法啟動線程,調用Runnable接口的子類的run方

     法。

代碼例子:

class Demo2 implements Runnable 

{

     //覆寫Runnable中的run方法

     public void run()

     {

        System.out.println("demo2 run");

      }

}

class RunnableDemo

{

     public static void main(String[] args) 

     {

          //定義一個Runnable接口的子類對象。

          Demo2 d=new Demo2();

         Thread t = new Thread(d);

         //調用Thread類中的start方法啟動線程,并調用Runnable接口中的run

         方法。*/

        t.start();

      }

}

3、線程的兩種建立方式有什麼差別嗎?

  3.1、繼承Thread類,線程代碼存方法在Thread類的run方法中。

      實作Runnable接口,線程代碼存方法在Runnable接口的run方法中。

  3.2、實作方式的好處:

       避免了單繼承的局限性,在定義線程時,使用實作方式能夠對外提供

       功能,而且還能被多線程操作。

代碼例子:

售票:

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

class Ticket implements Runnable 

{

      //定義100張票。

      private int ticket=100;

      public void run()

      {

           while(true)

           {

               if(ticket>0)

               {

                   //擷取線程的名稱,以及各個線程買票的情況。

                   System.out.println(Thread.currentThread().getName()+"..."+ticket--);

                 }

             }

         }

}

class TicketDemo

{

     public static void main(String[] args) 

     {

            Ticket tic=new Ticket();

            //建立4個線程。

            Thread t1 = new Thread(tic);

            Thread t2 = new Thread(tic);

            Thread t3 = new Thread(tic);

            Thread t4 = new Thread(tic);

             t1.start();

             t2.start();

             t3.start();

             t4.start();

       }

}

(6)多線程安全問題:

 問題出現的狀況:

     當多個線程在操作同一個共享資料時,一個線程的多條語句中隻執行了一

 部分,還沒有執行完,另一條線程就進來執行了,導緻共享資料的錯誤。

 解決辦法:

     對于有多條操作共享資料的語句,隻能讓一個線程全部執行完,在執行的過程中,其它的線程不能參與執行。 

Java對于多線程的安全問題提供了專業的解決方法。

1、同步代碼塊來實作安全:

synchronized(對象) 

{

   需要被同步的語句;

}

1.1、那麼那些資料需要被同步呢?

看那些語句在操作共享資料,就同步這些語句。

1.2、裡面存放的對象如同鎖,沒有鎖的線程就算取得了CPU的執行權也不能執行,線程加鎖帶來的弊端:要有鎖對象,是以耗資源,要判斷鎖,是以效率稍減。

  2、同步函數

  public synchronized void method(Type args)

 {

      需要被同步的語句; 

  }

2.1、同步非靜态函數用的鎖是this。

2.2、如果同步靜态函數:所用的鎖不是this,因為靜态方法中不能出現this,

    用的是 類名.class是Class類型對象。

3、如果一個程式中有安全問題,使用同步時應注意:

3.1、明确多線程運作代碼(一般為run方法裡調用的語句,以及其附帶語句(調   

     用了其它的方法)有哪些 

3.2、明确共享資料。

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

4、同步的前提:

1、必須有兩個或兩個以上的線程。

2、必須是多個線程使用同一把鎖。

解釋:也就是保證同步中隻能有一個線程在運作。

(7)單例設計模式

1、單例設計模式的作用:

   解決一個類在記憶體中隻用一個對象。

2、 餓漢式步驟:

   2.1、将構造函數私有化。

   2.2、在類中建立一個私有并靜态的本類對象。

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

代碼例子:

class Single1

{

     //将構造函數私有化。

     private Single1(){}

     //在類中建立一個私有并靜态的本類對象。

     private static Single1 s1 =new Single1();

      //對外提供一個方法可以擷取到該對象。

     public static Single1 getInstance()

     {

       return s1;

      }

}

class Single1Demo

{

      public static void main(String[] args) 

      {

          //用類名調用擷取對象的方法。

          Single1 ss1 =Single1.getInstance();

       }

}

3、懶漢式步驟:

  3.1、私有并靜态一個對象的引用。

  3.2、将構造函數私有化。

  3.3、對外提供一個方法,加入同步代碼塊,當方法被調用時才會初始化。

代碼例子:

class Single2

{

    private static Single2  s2 = null;

    private Single2 (){}

    public static Single2 getInstance()

   {

        if(s==null) 

        {

           synchronized(Single2.class)

           {

                 if(s2==null) 

                 {

                     s2 = new Single();

                  }

             }

          }

          return s2;

      }

      public static void main(String[] args) 

      {

          Single2 ss2 = Single2.getInstance();

       }

}

解釋:

    當a線程進來的時候,判斷s2==null,滿足條件,就擷取了鎖,但是它擷取鎖以後,執行權被b線程搶走了,它就停在這了,擷取執行權的b線程進來判斷s2==null,滿足條件,往下執行的時候發現已經上鎖了,是以它沒有進去同步代碼塊就停下了,并且釋放了執行權這時a線程擷取了執行權,繼續往下執行,判斷為空,就建立了一個對象,并且釋放了鎖,于是b線程又獲得了執行權,它進來同步一看,s2不等于空了,于是它就釋放了執行權,這時又有一條線程c進來了,它判斷s2不等于空,就不用再判斷鎖了。

4、餓漢式和懶漢式有什麼差別?

 4.1、懶漢式特點:執行個體的延時加載。

 4.2、懶漢式在多線程通路時存在安全問題:

      擷取執行個體的方法中有多條語句操作共享資源,是以用同步代碼塊和同步函數都能解決此問題,但效率稍低;可以用雙重判斷來減少判斷鎖的次數。

 4.3、懶漢式加同步時使用的鎖是哪個?

     這個鎖是該類所屬的位元組碼檔案對象。

(8)死鎖:

  1、死鎖的産生:同步中嵌套同步,而使用的鎖不一樣。

代碼例子:

 class DeadLock 

{

   public static void main(String[] args)

   {

        Test a = new Test(true);

        Test b = new Test(false);

        Thread t1 = new Thread(a);

        Thread t2 = new Thread(b);

        t1.start();

        t2.start();

    }

}

class Test implements Runnable 

{

     private boolean flag;

     Test(boolean flag)

     {

        this.flag = flag;

     } 

     public void run() 

     {

         if(flag)

         {

             synchronized(MyLock.locka)

             {

                 System.out.println("if...locka");

                 synchronized(MyLock.lockb) 

                 {

                    System.out.println("if...lockb");

                 }

              }

         }

         else 

         {

             synchronized(MyLock.lockb) 

             {

                System.out.println("else...lockb");

                 synchronized(MyLock.locka)

                 {

                  System.out.println("else...locka");

                }

            }

         }

    }

}

class MyLock 

{

    static Object locka = new Object();

    static Object lockb = new Object();

}

結果是:一個拿着a鎖不放,一個拿着b鎖不放,拿着a鎖的進不了b鎖,

               拿着b鎖的進不了a鎖。 

(9)多線程通信

多線程通信:即多個線程操作同一個資源,但操作的動作不同。

1、等待喚醒機制:

原理:輸入線程如果擷取到了CPU的執行權,它存了一個 zhangsan,man,它存的 

     時候别的線程進不來,它存完出了同步以後,輸入線程和輸出線程都有可

     能搶到CPU的執行權,是以說輸入線程可能有搶到了執行權,是以它又把

     “麗麗”,“女女女女”存進去了,然後不斷的搶,不斷的存,不段的覆寫

     到某一時刻時,輸出線程搶到了執行權,然後開始列印,但是它不可能隻

     搶到一次,是以就不斷的列印同一個名字和性别,是以列印的時候是一大

     片相同的,而不是間隔開來的。但是我們想要的是輸入一個就跟着輸出一

     個,是以就用到了等待喚醒機制,那麼就是定義一個标記,當輸入線程判

     裡面沒有名字時,也就是為false,然後就往裡面存一個名字,存完以後輸

     入線程會把false改為true,此時輸入線程還持有執行權,是以它進來以判

     斷标記為true就不存了,然後它就等待了,等待了也就是當機了,也就是

     放棄了執行資格,然後輸出線程就獲得了執行權,那麼它進去一判斷标記

     為true,說明有資料,然後就列印一次,列印完以後吧标記改為false,此

     輸出線程仍持有執行權,當它再去判斷的時候發現标記為false,是以就等

     待了,也就是放棄了執行資格,等待之前它把輸入線程喚醒了,然後輸入

     線程繼續存資料。

代碼例子:

public class ThreadCommunication

{

public static void main(String[] args)

 {

       Res r = new Res();

       new Thread(new Input(r)).start();

       new Thread(new Output(r)).start();

    }

}

//共享資源

class Res

 {

    private String name;

    private String sex;

    private boolean flag = false;

    //設定方法

    public synchronized void set(String name, String sex)

    {

        //flag=true表示設定過的還未列印,有資料則等待

         if(flag) 

          try

          {

           this.wait();

          }

          catch(InterruptedException e)

          {

            e.printStackTrace();

             }

          this.name = name;

          this.sex = sex;

          flag = true;

          this.notify();

     }

        //列印方法

     public synchronized void out()

     {

        if(!flag)

            try

            {

              this.wait();

             }

             catch(InterruptedException e)

             {

                      e.printStackTrace();

             }

            System.out.println(name + ".........." + sex);

            flag = false;

             this.notify();

      }

}

//輸入線程

class Input implements Runnable

{

      private Res r;

      Input(Res r)

      {

          this.r = r;

       }

       public void run() 

       {

          int x = 0;

          while(true)

          {

             if(x==0) 

              {

                 r.set("zhangsan", "man");                   

               }

              else

              {

                  r.set("麗麗", "女女女女");                    

               }

               x = (x+1)%2;//控制x在0和1之間不斷交替,進而讓設定的内容不同。

        }

    }

}

//輸出線程

 class Output implements Runnable

 {

        private Res r;

        Output(Res r)

        {

           this.r = r;

         }

         public void run() 

        {

            while(true) 

            {

                r.out();

              }

          }

  }

2、總結:

2.1、線上程池裡等待的線程,往往先喚醒的是第一個。

wait()、notify()、notifyAll()。目前線程必須擁有此對象螢幕(即鎖),鎖隻

有在同步中才有。

2.2、那麼上述方法為何被定義在Object類中?

因為上述方法由鎖調用,鎖可以是任意對象,是以定義在任意對象的父類

Object中。

2.3、wait()和sleep()有何差別:

    wait()釋放資源,釋放鎖。

    sleep()釋放資源,不釋放鎖。

等待和喚醒的必須是同一個鎖。而鎖可以是任意對象,是以定義在Object

中。

(10)Lock接口和Condition接口

1、JDK1.5中提供了替代同步中隐式鎖的synchronized為顯式鎖方式Lock接口和

   Condition接口将Object中的wait(),notify(),notifyAll()替換成了Condition對象

   的avait(),signal(),signalAll()方法,Condition對象可以由Lock擷取,實作了本方隻 

   會喚醒對方的操作。     

2、生産者消費者問題替代方案:

   1.5版本時,提供了顯式的鎖機制,以及顯式的鎖對象上的等待喚醒操作機制

   一個Lock鎖,對應了多個Condition。

代碼例子:

public class ProducerConsumerJDK5

 {

   public static void main(String[] args) 

  {

        Resource res = new Resource();

        Producer p1 = new Producer(res);

        Consumer c1 = new Consumer(res);

        Thread t1 = new Thread(p1);

        Thread t2 = new Thread(p1);

        Thread t3 = new Thread(c1);

        Thread t4 = new Thread(c1);

        t1.start();

        t2.start();

        t3.start();

        t4.start();    

    }

}

class Resource 

{

    private String name;

    private int count =1;

    boolean flag = false;

    Lock lock = new ReentrantLock();

    Condition condition_pro = lock.newCondition();

    Condition condition_con = lock.newCondition();

    public void set(String name) throws Exception 

    {

        lock.lock();

        try 

        {

           /*用while循環判斷生産标記,讓被喚醒的線程再次判斷标記,確定

            不會出現生産一個而消費兩個的情況*/

            while(flag)

            condition_pro.await();           

            this.name = name + "..." + count++;

            //讓生産進度變慢,消費進度也随之變慢

            Thread.sleep(100);

          System.out.println(Thread.currentThread().getName() +" 生産 "+ 

                                                         this.name);

            //生産後将生産标記設為true,意為已經生産一個了,快列印。

            flag = true;

             condition_con.signal();//激活對方線程。

          }

          finally 

          {

              lock.unlock();//釋放鎖的動作一定要執行

           }

    }

    public void out() throws Exception 

    {

          lock.lock();

          try 

          {

             while(!flag) 

             condition_con.await();

             System.out.println(Thread.currentThread().getName() +" 消費 "+  this.name);           

             flag = false;

            condition_pro.signal();

            } 

            finally 

            {

                lock.unlock();

             }

        }  

    }

  class Producer implements Runnable

 {

       private Resource res;

      public Producer(Resource res)

      {

           this.res = res;

      }

      public void run() 

     {

        //不停的生産

        while(true) 

       {       

         try 

         {

           res.set("包子");

         }

         catch (Exception e)

         {

            e.printStackTrace();

         }

       }

    }

}

class Consumer implements Runnable

 {

       private Resource res;

      public Consumer(Resource res) 

      {

           this.res = res;

      }

      public void run()

      {

          while(true)

          {

              try 

              {

                 res.out();

              } 

              catch (Exception e)

              {

                e.printStackTrace();

              }

          }

     }

 }

3、停止線程:

3.1、直接結束run()方法。

   原理:開啟多線程運作,運作代碼通常是循環結構,隻要控制住循環,就可

         以讓run方法結束。

   特殊情況:當線程處于當機狀态的時候,就不會讀取到标記,那麼這時線程

            就不會結束。當機狀态不是停止線程,是将線程挂起。

3.2、使用interrupt()方法。

   強制清除其當機狀态,然後讓其恢複到運作狀态,然後就能讀标記了。

4、守護線程:

4.1、setDaemon(true):設定線程為守護線程(也可将其稱為背景線程)。

當正在運作的線程都是守護線程時,jvm即退出,該方法必須在啟動線程前調用。

4.2、前台線程和背景線程:在執行過程中沒有差別(同樣搶占cpu的執行權),

     隻在結束時有差別,守護線程依賴于主線程(前台線程),當主線程結束後,

     所有守護線程自動結束。    

5、等待該現場終止:

方法join(),臨時加入線程,當A線程執行到了B線程的B.join()方法,A線程就等待,直到B線程結束後,A線程才從當機狀态回到運作狀态。

一般使用方式:當滿足一定的條件時,讓某一線程加入進來。 

6、優先級:

所有的線程預設的優先級都是5,線程最高優先級是10,線程最低優先級是1。

這裡的main是線程組名(Thread-1由main開啟),線程預設的優先級是5,ThreadGroup,可以讓程式員自己建立線程組。(幾乎用不到)

setPriority(Thread.MAX_PRIORITY)設定優先級最高

Thread.yield():暫停目前正在執行的線程對象,并執行其他線程。

如果有兩個線程操作同一段代碼,代碼中加入Thread.yield(),運作的效果類似是a線程執行一次b線程執行一次,均勻交替的執行。

------- <a href="http://www.itheima.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow"  target="blank">android教育訓練</a>、<a href="http://www.itheima.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow"  target="blank">java教育訓練</a>、期待與您交流! ----------