天天看点

黑马程序员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>、期待与您交流! ----------