zoukankan      html  css  js  c++  java
  • 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个方法。
    • 生产者消费者

    七、线程池

    • 就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
    • 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 表达式完结

  • 相关阅读:
    正向代理和反向代理
    负载测试和压力测试
    cs 与 bs 架构
    什么是amcl
    一个故事告诉你比特币的原理及运作机制
    Tor Browser(洋葱浏览器)——一款使你匿名上网的浏览器
    CAS3.5.x(x>1)支持OAuth2 server
    帮你深入理解OAuth2.0协议
    使用Spring MVC统一异常处理实战
    tcpdump非常实用的抓包实例
  • 原文地址:https://www.cnblogs.com/_Moliao/p/12469855.html
Copyright © 2011-2022 走看看