07.异常、多线程、Lambda 表达式

一、异常

  • 指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
  • 异常体系
    • 根类
      • java.lang.Throwable
        • 两个直接子类
          • java.lang.Error
            • 严重错误Error,无法通过处理的错误,只能事先避免。
          • java.lang.Exception
            • 表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。
            • 这是我们平时所说的异常。

二、异常的处理

  • 五个关键字:try、catch、finally、throw、throws
  • throw
    • 可以使用 throw 关键字在指定的方法中抛出指定的异常。
    • 格式
      • throw new xxxException("异常产生的原因");
    • 注意事项
      • throw 关键字必须写在方法的内部。
      • throw 关键字后边 new 的对象必须是 Exception 或者 Exception 的子类对象。
      • throw 关键字抛出指定的异常对象,我们就必须处理这个异常对象。
        • throw关键字后边创建的是 RuntimeException 或者是 RuntimeException 的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
        • throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try...catch。
  • throws
    • 异常处理的第一种方式,交给别人处理。
    • 作用
      • 当方法内部抛出异常对象的时候,那么我们就必须处理这个异常对象。
      • 可以使用 throws 关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理(自己不处理,给别人处理),最终交给JVM处理-->中断处理。
    • 格式:在方法声明时使用。
      • 修饰符 返回值类型 方法名(参数列表) throws 异常类名1,异常类名2...{}
    • 注意事项
      • throws 关键字必须写在方法声明处。
      • throws 关键字后边声明的异常必须是 Exception 或者是 Exception 的子类。
      • 方法内部如果抛出了多个异常对象,那么 throws 后边必须也声明多个异常。
        • 如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可。
      • 调用了一个声明抛出异常的方法,我们就必须的处理声明的异常。
        • 要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM。
        • 要么 try...catch 自己处理异常。
  • try..catch
    • 处理异常的第二种方式,自己处理异常。
    • 格式
      • try{
        • 可能产生异常的代码
      • }catch(异常类型   变量名){
        • 处理异常
          •  一般在工作中,会把异常的信息记录到一个日志中。
      • }
      • ...
      • catch(异常类型   变量名){
      • }
    • 注意事项
      • try 中可能会抛出多个异常对象,那么就可以使用多个 catch 来处理这些异常对象。
      • 如果 try 中产生了异常,那么就会执行 catch 中的异常处理逻辑,执行完毕 catch 中的处理逻辑,继续执行 try...catch 之后的代码。
      • 如果 try 中没有产生异常,那么就不会执行 catch 中异常的处理逻辑,执行完 try 中的代码,继续执行 try...catch 之后的代码。
  • finally 代码块
    • 格式
      • try{
        • 可能产生异常的代码
      • }catch(异常类型   变量名){
        • 处理异常
          • 一般工作中,会把异常的信息记录到一个日志中。
      • }
      • ...
      • catch(异常类型   变量名){
      • }finally{
        • 无论是否出现异常都会执行。
      • }
    • 注意事项
      • finally不能单独使用,必须和 try 一起使用。
      • finally 一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放 (IO) 。
  • 异常注意事项
    • 多个异常使用捕获如何处理
      • 多个异常分别处理。
      • 多个异常一次捕获,多次处理。
        • 我们常用的方式,处理时需要注意:
        • catch 里边定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上边,否则就会报错。
      • 多个异常一次捕获一次处理。
    • 运行时异常被抛出可以不处理。即不捕获也不声明抛出。
      • 默认给虚拟机处理,终止程序。
    • 如果 finally 有 return 语句,永远返回 finally 中的结果,避免该情况。
    • 如果父类抛出多个异常,子类复写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
    • 父类方法没有抛出异常,子类重写父类方法时也不可抛出异常。
      • 此时子类产生异常,只能捕获处理,不能声明抛出。
  • 自定义异常
    • Java 提供的异常类,不够我们使用,需要自己定义一些异常类。
    • 格式
      •  public  class  XXXExcepiton  extends  Exception | RuntimeException{
        • 添加一个空参数的构造方法
        • 添加一个带异常信息的构造方法
          • 查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息。
      • }
    • 注意事项
      • 自定义异常类一般都是以 Exception 结尾,说明该类是一个异常类。
      • 自定义异常类,必须的继承 Exception 或者 RuntimeException 。
        • 继承 Exception
          • 自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么 throws,要么 try...catch 。
        • 继承 RuntimeException
          • 自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)。

三、多线程

  • 并发与并行
    • 并发
      • 指两个或多个事件在同一时段内发生。
    • 并行
      • 指两个或多个事件在同一时刻发生(同时发生)。
  • 线程与进程
    • 进程
      • 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
    • 线程
      • 线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
    • 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
    • Java 程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一个优先级,随机选择一个执行。
  • Thread 类
    • 构造方法
      • public   Thread();
        • 分配一个新的线程对象。
      • public   Thread(String   name)
        • 分配一个带指定名字的新的线程对象。
      • public   Thread(Runnable   target)
        • 分配一个带有指定目标的新的线程对象。
      • public   Thread(Runnable   target , String   name)
        • 分配一个带有指定目标的新的线程对象并指定名字。
    • 常用方法
      • String   getName()
        • 返回该线程的名称。
      • void   setName(String   name)
        • 改变线程名称,使之与参数 name 相同。
      • static   Thread   currentThread()
        • 返回对当前正在执行的线程对象的引用。
      • static   void   sleep(long   millis)
        • 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
          • 毫秒数结束之后,线程继续执行。
  • 创建多线程
    • 第一种方式
      • 继承 Thread 类
        • 实现步骤
          • 创建一个 Thread 类的子类。
          • 在 Thread 类的子类中重写 Thread 类中的 run 方法,设置线程任务(开启线程要做什么?)
          • 创建 Thread 类的子类对象。
          • 调用 Thread 类中的方法 start 方法,开启新的线程,执行 run 方法。
            • void start()
              • 使该线程开始执行。
              • Java 虚拟机调用该线程的 run 方法
    • 第二种方式
      • 实现 Runnable 接口。

        • 实现步骤
          • 创建一个 Runnable 接口的实现类。
          • 在实现类中重写 Runnable 接口的 run 方法,设置线程任务。
          • 创建一个 Runnable 接口的实现类对象。
          • 创建 Thread 类对象,构造方法中传递 Runnable 接口的实现类对象。
          • 调用 Thread 类中的 start 方法,开启新的线程执行 run 方法。
    • 实现 Runnable 接口创建多线程程序的好处。
      • 避免了单继承的局限性
      • 增强了程序的扩展性,降低了程序的耦合性(解耦)。
        • 实现 Runnable 接口的方式,把设置线程任务和开启新线程进行了分离(解耦)。
        • 实现类中,重写了run方法:用来设置线程任务。
        • 创建 Thread 类对象,调用start方法:用来开启新线程。
  • 匿名内部类实现线程的创建
    • 格式
      • new 父类/接口(){
        • 重复父类/接口中的方法
      • }

四、线程安全

  • 通过模拟多窗口卖票引出线程安全问题。
    • 卖出不存在的票和重复的票。
  • 解决线程安全问题的方案
    • 使用同步代码块
      • 格式
        • synchronized (锁对象){
          • 能会出现线程安全问题的代码(访问了共享数据的代码)
        • }
      • 同步代码块中的锁对象,可以使用任意的对象。
      • 但是必须保证多个线程使用的锁对象是同一个。
      • 锁对象作用
        • 把同步代码块锁住,只让一个线程在同步代码块中执行。
    • 使用同步方法
      • 使用步骤
        • 把访问了共享数据的代码抽取出来,放到一个方法中。
        • 在方法上添加 synchronized 修饰符。
      • 格式
        • 修饰符 synchronized 返回值类型 方法名(参数列表){
          • 可能会出现线程安全问题的代码(访问了共享数据的代码)
        • }
      • 隐含的锁对象是谁
        • 非 static 方法
          • 谁调用这个方法,锁对象就是谁。
            • 也就是 this 。
        • satic 方法
          • 不能是 this
            •  this 是创建对象之后产生的,静态方法优先于对象。
          • 是当前方法所在类的字节码对象(类名.class)。
    • 使用 Lock 锁
      • java.util.concurrent.locks.Lock接口
        • Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
      • Lock 接口中的方法
        • void lock()
          • 获取锁。
        •  void unlock()
          • 释放锁。
      • 实现步骤
        • 在成员位置创建一个 ReentrantLock 对象。
        • 在可能会出现安全问题的代码前调用 Lock 接口中的方法 lock 获取锁。
        • 在可能会出现安全问题的代码后调用 Lock 接口中的方法 unlock 释放锁。

五、线程状态

  

  • Timed Waiting (计时等待)
    • 一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。
    • 进入到 TimeWaiting(计时等待) 有两种方式
      • 使用sleep(long  m) 方法
        • 在毫秒值结束之后,线程睡醒进入到 Runnable/Blocked 状态。
      • 使用wait(long  m) 方法
        • wait 方法如果在毫秒值结束之后,还没有被 notify 唤醒,就会自动醒来,线程睡醒进入到 Runnable/Blocked 状态。
    • 唤醒的方法
      • void   notify()
        • 唤醒在此对象监视器上等待的单个线程。
      • void   notifyAll()
        • 唤醒在此对象监视器上等待的所有线程。
  • BLOCKED (锁阻塞)
    • 一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。
      • 线程A与线程B代码中使用同一锁,如果线程 A 取到锁,线程 A 进入到 Runnable 状态,那么线程 B 就进入到 Blocked 锁阻塞状态。
  • Waiting (无限等待)
    • 一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
      • 如何进入
        • void wait();
      • 如何唤醒
        • void notify()
        • void notifyAll()
    • 一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的 Object.notify() 方法或 Object.notifyAll() 方法。
      • 也就是等待唤醒机制。

六、等待唤醒机制

  • 线程间通信
    • 概念
      • 多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
    • 为什么要处理线程间通信
      • 多个线程并发执行时, 在默认情况下 CPU 是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
    • 如何保证线程间通信有效利用资源
      • 多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
  • 等待唤醒机制
    • 就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll() 来唤醒所有的等待线程。
      • wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中。
      • notify:选取所通知对象的 wait set 中的一个线程释放。
      • notifyAll:则释放所通知对象的 wait set 上的全部线程。
        • 注意
          • 哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
      • 总结如下
        • 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
        • 否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态。
      • 细节
        • wait方法与notify方法必须要由同一个锁对象调用
          • 因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的 wait 方法后的线程。
        • wait方法与notify方法是属于Object类的方法的。
          • 因为:锁对象可以是任意对象,而任意对象的所属类都是继承了 Object 类的。
        • wait方法与notify方法必须要在同步代码块或者是同步函数中使用
          • 因为:必须要通过锁对象调用这2个方法。
  • 生产者消费者

七、线程池

扫描二维码关注公众号,回复: 9819296 查看本文章
  • 就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
  • java.util.concurrent.Executor
    • 线程池的顶级接口
      • 严格意义上讲并不是一个线程池,只是一个执行线程的工具。
      • 真正的线程池接口是 java.util.concurrent.ExecutorService。
  • java.util.concurrent.Executors
    • 线程池的工厂类,用来生成线程池。
      • static   ExecutorService   newFixedThreadPool(int    nThreads)
        • 创建一个可重用固定线程数的线程池。
        • int   nThreads
          • 创建线程池中包含的线程数量。
        • 返回值
          • 返回的是 ExecutorService 接口的实现类对象,我们可以使用 ExecutorService 接口来接收。
          • 面向接口编程。
        • 获取到了一个线程池 ExecutorService 对象,如何使用。
          • public   Future<?>   submit(Runnable   task)
            • 获取线程池中的某一个线程对象,并执行。
            • Future 接口
              • 用来记录线程任务执行完毕后产生的结果。
          • void   shutdown()
            • 关闭/销毁线程池。
    • 线程池使用步骤
      • 使用线程池的工厂类 Executors 里边提供的静态方法 newFixedThreadPool 生产一个指定线程数量的线程池。
      • 创建一个类,实现Runnable接口,重写run方法,设置线程任务。
      • 调用 ExecutorService 中的方法 submit ,传递线程任务(实现类),开启线程,执行run方法。
      • 调用 ExecutorService 中的方法 shutdown 销毁线程池(不建议执行)。
        • 线程池都没有了,就不能获取线程了。

八、Lambda 表达式

  • 引入
    • 传统通过匿名内部类创建多线程方式
      • 部分代码
        • new Thread(new Runnable(){
          • @Override
          • public void run() {
            • System.out.println(Thread.currentThread().getName()+" 新线程创建了"
          • }
        • }).start();
      • 使用Lambda表达式写法
        • new Thread(()->{
          •  System.out.println(Thread.currentThread().getName()+" 新线程创建了");
          • }
        • ).start();
      • 优化省略Lambda写法
        • new Thread(()->System.out.println(Thread.currentThread().getName()+" 新线程创建了")).start();
  • 2014年3月 Oracle 所发布的 Java 8(JDK 1.8)中,加入了Lambda表达式的重量级新特性,为我们打开了新世界的大门。
    • Lambda表达式的标准格式
      • 由三部分组成
        • 一些参数
        • 一个箭头
        • 一段代码
      • 格式
        • (参数列表) ->{一些重写的方法}
      • 解释
        • ()
          • 接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔。
        • ->
          • 传递的意思,把参数传递给方法体 {}。
        • {}
          • 重写接口的抽象方法的方法体。
    • 省略格式
      • Lambda表达式:是可推导,可以省略。
        • 凡是根据上下文推导出来的内容,都可以省略书写。
        • 可省略的内容
          • 参数列表
            • 括号中参数列表的数据类型,可以省略不写。
            • 括号中的参数如果只有一个,那么类型和 () 都可以省略。
          • 一些代码
            • 如果 {} 中的代码只有一行,无论是否有返回值,都可以省略({},return,分号) 
              • 注意:要省略 {},return。分号必须一起省略

九、异常、多线程、Lambda 表达式完结

猜你喜欢

转载自www.cnblogs.com/_Moliao/p/12469855.html
今日推荐