06.面向对象(三)【异常】【包】【多线程】

来源:互联网 发布:java 验证身份证 编辑:程序博客网 时间:2024/06/06 01:18

 

一.面向对象的异常和包

1.异常

   (1)概念的理解:异常指的是程序运行时出现的不正常情况。如同汽车出了故障不能行驶,程序抛出异常后

                   也将无法正常工作,必须要先处理异常问题,不能处理的,直接将程序停止。

                   

                   出现异常时,将先处理异常,后面的语句不执行。
                   简单说,异常的发生,将功能停止。

       

       异常类:在java中,通过面向对象的思想,用类的形式对不正常的情况进行描述和封装对象。

              不同的问题用不同的类描述,如角标越界,空指针,ConcurrentModificationException等。

              

              问题越多,意味着描述的异常类越多,将其共性内容不断的向上抽取,就形成了异常体系。

      

              异常体系的特点:子类的后缀名都是用其父类名作为后缀,阅读性很强。
 

       最终问题分成两大类:

           1.一般不可处理的,即Error。

                 Error是由JVM虚拟机抛出的严重性的问题。这种问题一旦发生,一般不针对性处理,

                 直接修改程序。

            2.可以处理的,即为Exception。

   

   (2)可抛性的理解

           Throwable:无论是error,还是exception,问题发生就应该可以抛出,让调用者知道并处理。
                      该体系的特点就在于Throwable及其所有的子类都具有可抛性。

           其实就是通过两个关键字来体现的:throws,throw。凡是可以被这两个关键字所操作的类和对象都

            具备可抛性。

 

   (3)throws和 throw的区别:
        1.throws使用在函数上。
           throw使用在函数内。
  
        2.throws抛出的是异常类,而且可以抛出多个,用逗号隔开。
          throw抛出的是异常对象。

 

   (4)处理异常的方式:

       ①声明。在可能抛出异常的函数后面用throws申明该异常类,以便调用者使用功能时很快明白可能出现

               的问题。并在函数内部抛出该异常对象。

         黑马程序员鈥斺斆嫦蚨韵螅ㄈㄒ斐#+多线程

       ②捕捉。这是可以对异常进行针对性处理的方式。

        异常捕捉的格式:

         
try//接收功能抛出的异常。
{
 //需要被检测异常的代码。
}

catch(异常类 变量)//该变量用于接收发生的异常对象。
{
 //处理异常的代码。
}

finally
{
 //一定会被执行的代码。
}

        一个功能如果抛出了多个异常,那么调用时,必须有对应多个catch进行针对性处理。
    内部有几个需要检测的异常,就抛出几个异常,抛出几个,就catch几个。

   (5)什么时候catch,什么时候throws呢?(在调用者那儿)
       功能内容可以解决,用catch。
       解决不了,用throws告诉调用者,由调用者解决。

 

  (6)细节:多态的catch中,如果有父类的catch在,一定要将父类的catch放在最后,
             如果放在第一个,所有异常都所属于这个父类catch,将先给第一个catch来处理。

       

       catch (Exception e)//多catch时,父类的catch放在最下面。
       {
   
        }

 

   (7)finally说明

       举个例子,连接数据库时一般有两个动作:查询和关闭连接。查询时,数据库会规定一定的时间,访问

    数据库时,无论查询是正常的还是异常。必须在有限时间内。超过规定时间,数据库会自动关闭连接,以释

   放资源。所以,关闭连接是必须要执行的。这时,“关闭连接”就放在finally代码块中实现。

 

    在trycatch finally代码块组合中,当没有必要资源需要释放时,可以不定义finally。但是, 异常无法

   直接catch处理,但是资源需要关闭时,需要定义finally。

        

2.自定义异常

     对于角标是正数不存在,可以用角标越界来表示。对于负数为角标的情况,则可用负数角标异常来表示。

 可是负数角标异常在java中没有定义。我们可以按照java异常的创建思想,用面向对象思想将负数角标异常进

  行自定义描述,并封装成对象。这种自定义的问题描述称为自定义异常。

     如果让一个类称为异常类,必须要继承异常体系。只有称为异常类体系的子类才有资格具备可抛性。

  才可以被throws ,throw关键字操作。

 

  而异常的分类有:

     (1)编译时被检测异常:只要是Exception和其子类都是。除了特殊子类RuntimeException体系。
              这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理方式。

    (2)编译时不受检测异常(运行时异常):RuntimeException和其子类。
              这种问题的发生,无法让功能继续,运算无法进行,更多是因为调用者的原因导致的,或者引发

              了内部状态的改变而导致的。
             这种问题一般不处理,直接编译通过,在运行时,让调用者运行程序时强行停止,让调用者修改
             代码。

 

   所以自定义异常时,要么继承Exception,要么继承RuntimeException。

 

3.异常的处理事项:

   (1)子类在覆盖父类的方法时,父类的方法如果抛出了异常。那么子类的方法只能抛出父类的异常或者该

        异常的子类。或者不抛。但是,不能抛出其他异常。

    (2)如果父类抛出多个异常,那么子类只能抛出父类异常的子集。
        简单说,子类覆盖父类只能抛出父类的异常或者子类或者子集。

    

     注意:如果父类的方法没有抛出异常,那么子类覆盖时绝对不能抛出。

 

 

4.包(package)

   (1)Jar包

       Jar:java的压缩包。

   

       生成压缩包的命令行: jar -cf haha.jar pack

       说明: c  创建归档目录
               指定归档文件名
             pack  表示把pack这个包压缩。
             haha 生成压缩包的文件名

             jar -xvf haha.jar  解压压缩包。
        但是一般不需要解压,只需要设置classpath= ./haha.jar。就是说,
jar包可以直接使用。
  

   (2)包的理解

           包其实也是一种封装形式。它可以对类文件进行分类管理,给类提供多层命名空间。

       所以,类名的全称为:包名.类名。

    

        包名中所有字母都是小写的。

 

   (3)导入包:导入包是为了简化类名书写。

       导包的原则是:用到哪个类,就导入哪个类。

   

    

       注意:包与包之间的类进行访问,被访问的包中的类必须是public的,被访问的包中的类的方法也必须

             是public的。

 

二.多线程

1.进程和线程

  进程:正在进行中的程序。

 

 线程:就是进程中一个负责程序执行的控制单元(执行路径)。一个进程至少要有一个线程。 

       每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

2.多线程:一个进程中可以同时执行多条路径,称为多线程。开启多线程是为了同时运行多部分代码。

 

  多线程的好处和弊端:

     好处:解决了多部分代码同时运行的问题。

     弊端:线程太多会导致效率降低。

 

  其实,应用程序的执行都是CPU在做着快速的切换完成的。
      单核的时候,其实程序不是同时运行,只是切换速度快,肉眼分辨不出而已。
      双核的时候,才有可能真正实现同时运行。

 

3.创建线程方式一:继承Thread类

 

 (1) 步骤:
      ①定义一个类继承Thread类。
      ②覆盖Tread类中的run方法。
      ③将要运行的代码封装到run方法,创建Thread子类对象,创建线程。
      ④调用start方法开启线程并调用线程的任务run方法,执行。

     创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码同时运行。  

    运行的指定代码就是执行路径的任务。jvm创建的主线程的任务都定义在了主函数中。

   

 (2)自定义线程的任务

     Thread类用于描述线程,线程是需要任务的,所以Thread类也有对任务的描述。
  这个任务就通过Thread类中的run方法来体现的。
  也就是说,run方法就是封装自定义线程运行任务的函数。

 (3)调用run和调用start方法的区别:

        创建线程后,如果不调用start()方法而直接调用run()方法时,自定义的线程没有被开启,run方

      法还是在主线程中运行,就是简单的对象调用功能。 不论有多少个对象调用run方法,都会按照主线程来

     运行,不存在cpu切换的情况。
        但是调用start()方法,会开启线程,并调用run方法执行线程任务。
     而且如果是多个自定义线程同时运行,CPU会随机快速切换来运行线程。

 

 

    小细节:

    黑马程序员鈥斺斆嫦蚨韵螅ㄈㄒ斐#+多线程

Demo d1 = new Demo("旺财");Demo d2 = new Demo("xiaoqiang");d1.start();//start()的作用是开启线程,调用run()方法。d2.start();//cpu线程切换是随机的,所以结果也不会是顺序的。System.out.println("over"+Thread.currentThread().getName());//说明cpu切换线程是随机的。


 (4)通过继承Thread类来创建线程的弊端:

         ①继承Thread类目的仅仅是为了覆盖run方法,而现在子类继承了Thread类中所有的方法。

         ②当一个类有自己的父类的时候,不能再继承Thread类。java不能多继承。只能有一个父类。

           这时候,就需要用别的方法来创建线程。

 

4.创建线程方式二:实现Runnable接口

     (1)创建线程的步骤:

         ①定义类实现Runnable接口。

         ②在类中定义run方法,覆盖Runnable接口的run方法。并将线程的任务代码封装到类中的run方法。

         ③通过Thread类创建线程对象,并将接口的子类对象作为Thread类的构造函数的参数进行传递。

           因为线程的任务都封装在子类对象的run方法中。所以在线程对象创建前就要明确线程的任务。

         ④调用线程对象的start方法开启线程。

 

     (2)实现Runnable接口的好处(和继承Thread类的区别):
         将线程的任务从线程的子类分离,进行了单独的封装。按照面向对象的思想将任务封装成对象。
         避免了java单继承的局限性。子类如果有了自己的父类,但是有一部分代码需要单独开辟线程来执

           行。但是java只能单继承,不能多继承,所以该子类不能再继承Thread类。不过可以使用接口来扩

           展额外功能。就是实现Runnable.

         所以,此创建线程的方法较为常用。

 

    (3)细节:没有接口子类对象传递时,调用的是自身的run方法,
               有接口子类对象传递时,调用的是子类对象中的run方法。

    

5. 同步代码块

  (1)线程安全问题

      线程安全问题产生的原因:

          ①多个线程在操作共享的数据。

          ②操作共享数据线程代码有多条。

      当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。
   就会导致线程安全问题的产生。

 

      解决线程安全的问题就是,将操作共享数据的代码封装,当一个线程执行这些代码时,其他代码不能参

  与运算。只有当前线程运算完这些代码后,其他线程才能参与运算。

      这就是同步代码块存在的意义。

  (2)同步代码块

       同步代码块就是为了解决线程安全问题而存在。

      

      其格式是:

       synchronized(对象)

      {

               //需要被同步的代码。

      }

      ①同步代码块的原理

           一个线程进来后,占据了标志的值,其他线程进来时,没有拿到标记位,进不来,所以就保证同步
       代码块每次只能有一个线程执行。相当于锁的功能,称为同步锁。

     ②同步代码块的利弊

           好处:解决了线程安全的问题。

           弊端:同步外的线程每次都要判断锁,效率降低。

     

     ③同步的前提:必须有多个线程并共用一个锁。

                   一般用Object类的对象。但是一定要定义为成员变量。不然多线程用的不是同一个锁。

  (3)同步函数

           同步函数同样可以解决线程安全的问题,它是同步代码块的简写。因为它的锁默认为this。

      所以只需要用synchronized修饰函数即可。

      格式一般为:public synchronized void show()

                  {}

      

         静态同步函数:静态函数没有this。需要修改成静态同步函数时,使用的锁是该函数所属的字节码文

     件对象。可以用getClass方法获取,也可以用当前 类名.class表示。

 

  (4)死锁

      死锁是由同步的嵌套引起的。如下图。同步函数里有同步代码块,代码块里又调用同步函数。

      黑马程序员鈥斺斆嫦蚨韵螅ㄈㄒ斐#+多线程

class Ticket implements Runnable{private static int num = 100;Object obj = new Object();booleanflag = true;public void run(){if(flag){while (true){synchronized(obj){show();}}}elsewhile(true)this.show();}public  synchronized void show(){synchronized(obj){if(num>0){try{Thread.sleep(10);}catch (InterruptedException e){}System.out.println(Thread.currentThread().getName()+"...sale..."+num--);}}}}


    
      对于死锁,在编程中应避免。

      针对同步嵌套,解决办法是:将多个锁都放到同一个锁中。即在嵌套外面,再加一层锁。这样在拿到

   其中一个锁之前,都要先拿到最外面的锁。

    

class Test implements Runnable{private boolean flag=true;Test(boolean flag){this.flag = flag;}public void run(){if(flag){while(true)synchronized(MyLock.lockc){synchronized(MyLock.locka){System.out.println(Thread.currentThread().getName()+"...if...locka");synchronized(MyLock.lockb){System.out.println(Thread.currentThread().getName()+"...if...lockb");}}}}else{while(true)synchronized(MyLock.lockc){synchronized(MyLock.lockb){System.out.println(Thread.currentThread().getName()+"...else...lockb");synchronized(MyLock.locka){System.out.println(Thread.currentThread().getName()+"...else...locka");}}}}}}class MyLock{public static final Object locka = new Object();public static final Object lockb = new Object();    public static final Object lockc = new Object();}class DeadLockTest {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();}}

(5)线程间的通信

          多个线程在处理同一资源,但是任务却不同。

          多线程运行时出现了安全问题,在各自任务中加了obj同步锁之后,还是会出现安全问题,同步的前

     提是同步中必须有多个线程并使用同一个锁。而现在在两个任务中使用了不同的锁。

          如果资源是唯一的,可以用资源做锁。

  (6)等待唤醒机制

       涉及的方法:
             ①wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
             ②notify():唤醒线程池中任意一个线程。
             ③notifyAll():唤醒线程池中的所有线程。

      这些方法都必须定义在同步中。因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪

   个锁上的线程。


      为什么操作线程的方法wait,notify,notifyAll定义在了Object类中?

           因为这些方法是监视器的方法,监视器其实就是锁。
           锁可以是任意的对象,任意的对象调用的方式之一定定义在Object类中

 

  (7)停止线程

      有两种方法:①stop方法结束

                   ②run方法结束

       控制线程的任务结束: 任务中都会有循环结构,只要控制住循环,就可以结束任务。
   控制循环通常就用定义标记来完成。

      但是如果线程处于冻结状态,无法读取标记,可以使用interrupt()方法将线程从冻结状态强制恢复到运

  行状态中来,让线程具备cpu的执行资格。但是强制动作会发生interruptedException,记得要处理。

 

  (8)wait 和sleep的区别:
      1.wait可以指定时间,也可以不指定。
        sleep必须指定时间。
      2.在同步中,对于cpu的执行权和锁的处理不同。
          wait:释放执行权,释放锁。
          sleep:释放执行权,不释放锁。(它不需要被人唤醒。)

 

 

 

 

原创粉丝点击