c++如何让程序异常以后继续执行_(七)异常与多线程

1.异常

(1)异常概念&异常体系

异常:指的是程序在执行过程中,出现非正常的情况,最终会导致JVM的非正常停止。

在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出一个异常的对象。Java处理异常的方式是中断处理。

异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行。

异常体系:

异常机制其实是帮助我们找到程序中的问题,异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Error与java.lang.Exception,平常所说的异常指的是java.lang.Exception

(2)异常分类

a420ce1379235df027dd765f7766e651.png

java.lang.Throwable:类是Java语言中所有错误或异常的超类

       Exception:编译期异常,进行编译(写代码)Java程序出现的问题

              RuntimeException:运行期异常,Java程序运行过程中出现的问题

              异常就相当于程序得了一个小毛病,把异常处理掉,程序可以继续执行

4dda22c9d574f19687016cec884487e1.png

       Error:错误

              错误就相当于程序得了一个无法治愈的毛病,必须修改源代码,程序才能继续执行

3b5588919dcf39dd23e57b902a37880a.png

(3)异常的产生过程解析

fc691b8f1703273dd064121851511445.png

(4)throw关键字

Java异常处理的五个关键字:try,catch,finally,throw,throws

throw关键字:

作用:可以使用throw关键字在指定的方法中抛出指定的异常

使用格式:throw new xxxException(“产生异常的原因”)

注意:

       1>throw关键字必须写在方法的内部

       2>throw关键字后边new的对象必须是Exception或者Exception的子类对象

       3>throw关键字抛出指定的对象,我们就必须处理这个对象

                     throw关键字后面创建的是RuntimeException或者RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)

                     throw关键字后面创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws要么try…catch

723d76c8640144df9e7c9c22f736bece.png

(5)Objects非空判断_requireNonNull

Objects类中的静态方法:

Public static T requireNonNull(T obj):查看指定引用对象不是null

源码:

       -public static TrequireNonNull(T obj){

              if(obj== null)

                     throw newNullPointerException();

              return obj;

}

(6)throws关键字_异常处理的第一种方式

throws关键字:异常处理的第一种方式,交给别人处理

作用:

       当方法内部抛出异常对象时,那么我们就必须处理这个异常

       可以使用throws关键字处理异常,会把异常对象声明抛出给方法的调用者处理(自己不处理,交给别人处理)最终交给JVM处理,-->中断处理

使用格式:在方法声明时使用

       修饰符返回值类型方法名(参数列表) throws AAAException,BBBException…{

              throw new AAAException(“产生原因”);

              throw new BBBException(“产生原因”);

              …

}

注意:

       1>throws关键字必须写在方法声明处

       2>throws关键字后边声明的异常必须是Exception或Exception的子类

       3>方法的内部如果抛出了多个异常对象,那么throws后边也必须声明多个异常

              如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可

       4>调用了一个声明抛出异常的方法,我们就必须处理声明的异常

              要么继续使用throws声明抛出,交给方法调用者处理,最终交给JVM(中断处理)

              要么try…catch自己处理

c8939302ee29727830146f87e06937bf.png

(7)try_catch_异常处理的第二种方式

try…catch异常处理的第二种方式:

格式:

       try{

              可能产生异常的代码

}catch(定义一个异常的变量,用来接收try中抛出的异常对象){

       异常的处理逻辑,异常异常对象之后,怎么处理异常对象

       一般在工作中,会把异常的信息记录到一个日志中

}     

catch(异常类名变量名){}

注意:

       1>try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象

       2>如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完catch中的处理逻辑,继续执行try…catch之后的代码

         如果try中没有产生异常,那么就不会执行catch中的异常处理逻辑,执行完try中的代码,继续执行try…catch之后的代码

4d8250af2fb4380b5d1e09227e19a887.png

(8)Throwable类中3个异常处理的方法

Throwable类中定义了三个异常处理的方法:

StringgetMessage():返回此throwable的简短描述

String toString():返回此throwable的详细消息字符串

voidprintStackTrace():JVM打印异常对象默认此方法,打印的异常信息是最全面的

9924261e1b1b0fe996215d5da210466b.png

(9)finally代码块

finally代码块

格式:

try{

              可能产生异常的代码

}catch(定义一个异常的变量,用来接收try中抛出的异常对象){

       异常的处理逻辑,异常异常对象之后,怎么处理异常对象

       一般在工作中,会把异常的信息记录到一个日志中

}     

catch(异常类名变量名){

}

       finally{

       无论是否出现异常都会执行

}

注意:

1>finally不能单独使用,必须和try一起使用

2>finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要释放资源(IO)

(10)异常注意事项_多异常的捕获处理

异常的注意事项:

多个异常使用捕获又该如何处理?

1>多个异常分别处理

0238d92f058bdce849bfd608cf99adbb.png

2>多个异常一次捕获,多次处理

138925a18ba94091ef23bb8390d4c098.png

一个try多个catch注意事项:

       catch里面定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上面,否则就会报错

ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException

3>多个异常一次捕获,一次处理

4419bc54a9b54df36261c7ab992ea5c3.png

运行时异常被抛出可以不处理,即不捕获也不声明抛出。

默认给虚拟机处理,终止程序,什么时候不抛出运行时异常了,再来继续执行异常

3abc91550a358f0db1e995c4ce9ebc54.png

(11)异常注意事项_finally有return语句

3440a99e7e4e49430a4b4b80abade18c.png

(12)异常注意事项_子父类异常

子父类的异常:

       -如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者父类异常的子类或者不抛出异常

       -父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常,只能捕获处理,不能声明抛出

注意:父类异常是什么样,子类异常就什么样

8050ed8000095d73540ba0fe68bd9121.png

(13)自定义异常类

自定义异常类:

       Java提供的异常类,不够我们使用,需要自己定义一些异常类

格式:

       public class XXXException extendsException | RuntimeException{

              添加一个空参数的构造方法

              添加一个带异常信息的构造方法

}

注意:

       1>自定义异常类一般都是以Exception结尾,说明该类是一个异常类

       2>自定义异常类,必须继承Exception或者RuntimeException

              继承Exception:那么自定义的异常类就是一个编译器异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try…catch

              继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)

112684e5d5b7a66e75920b3bd0f0e348.png

(14)自定义异常类的练习

要求:模拟注册操作,如果用户名已经存在,则抛出异常并提示:亲,该用户名已经被注册

分析:

       1>使用数组保存已经注册过的用户名(数据库)

       2>使用Scanner获取用户输入的注册的用户名(前端,页面)

       3>定义一个方法,对用户输入过的用户名进行判断

              遍历存储已经注册过的用户名的数组,获取每一个用户名

              使用获取到的用户名和用户输入的用户名比较

                     true:用户名已经存在,抛出RegisterException异常,告知用户”亲,该用户名已经被注册了!”

                     false:继续遍历比较

如果循环结束了,还没有找到重复的用户名,提示用户”恭喜您,注册成功!”

0d2668d2fbde6f296f33f2f2b6a8f652.png

6d1246b562c6f879cc09b5703b143504.png

2.线程实现方式

(1)并发与并行

并发:指两个或多个事件在同一时间段内发生

并行:指两个或多个时间在同一时刻发生(同时发生)

4f761a9bf691a79be345434f56121395.png

(2)进程概念

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建,运行到消亡的过程。

330c81548ed87e1cf5708ac544035e93.png

(3)线程概念

线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称为多进程程序。

简而言之:一个程序运行后之后有一个进程,一个进程中可以包含多个线程

1c7de9963e4fbcbf7aa971302e11f4d5.png

(4)线程调度

分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。

抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调用。

b86d93df5a449e4bde6b44f66d63de70.png

(5)主线程

主线程:执行主(main)方法的线程

单线程程序:Java学习中只有一个线程

执行从main方法开始,从上到下依次执行

JVM执行main方法,main方法会进入到栈内存

JVM会找操作系统开辟一条main方法通向cpu的执行路径

cpu就可以通过这个路径来执行main方法

而这个路径有一个名字,叫main(主)线程

8a7d6352b768d32d85a11323409e213a.png

(6)创建多线程程序的第一种方式

创建多线程程序的第一种方式:创建Thread类的子类

java.lang.Thread类:是描述线程的类,想要实现多线程程序,就必须继承Thread类

实现步骤:

1>创建一个Thread类的子类

2>在Thread类的子类中重写Thread类中的方法run方法,设置线程任务(开启线程要做什么?)

3>创建Thread类的子类对象

4>调用Thread类中的方法start方法,开启新的线程,执行run方法

void start()使该线程开始执行;Java虚拟机调用该线程的run方法

结果是两个线程并发的执行,当前线程(main线程)和另一个线程(创建新的线程,执行run方法)

多次启动一个线程是非法的,特别是当线程已经执行结束后,不能再重新启动.

Java程序属于抢占式调度,哪个线程的优先级高,优先执行哪个线程;同一个优先级,随机选择一个执行.

(7)多线程原理_随机性打印结果

0fad7c21b0f44c5586a8d55cbd4213b2.png

(8)多线程原理_多线程内存图解

ffc6bf4379152c5ac445763a4394bed4.png

51da66e96334bf5a9800bdb0b03ffb85.png

(9)Thread类的常用方法_获取线程名称

获取线程的名称:

       1>使用Thread类中的方法getName()

              String getName()  返回该线程的名称

       2>可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称

              staticThread currentThread()  返回对当前正在执行的线程对象的引用

(10)Thread类的常用方法_设置线程名称(了解)

1>使用Thread类中的方法setName(名字)

       void setName()(String name):改变线程名称,使之参数name相同

2>创建一个带参数的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字

       Thread(String name):分配新的Thread对象

(11)Thread类的常用方法_sleep

 -public static void sleep(long millis):使当前正在执行的线程以指定的毫秒值进行暂停(暂时停止执行)

毫秒数结束之后,线程继续执行。

8b9d8112a37fdd6bb90d3c6d5b802d53.png

(12)创建多线程程序的第二种方式_实现Runnable接口

创建多线程程序的第二种方式:实现Runnable接口

java.lang.Runnable

       Runnable接口应该由那些打算通过某一线程执行其实例的类来实现.必须定义一个称为run的无参数方法.

java.lang.Thread方法类的构造方法

       Thread(Runnable target) :分配新的Thread对象

       Thread(Runnable target,String name):分配新的Thread对象

实现步骤:

       1>创建一个Runnable接口的实现类

       2>在实现类中重写Runnable接口的run方法,设置线程任务

       3>创建一个Runnable接口的实现类对象

       4>创建Thread类对象,构造方法中传递Runnable接口的实现类对象

       5>调用Thread类中的start方法,开启新的线程执行run方法

(13)Thread和Runnable的区别

实现Runnable接口创建多线程程序的好处:

1>避免了单继承的局限性

一个类只能继承一个类,类继承了Thread类就不能继承其他的类

实现了Runnable接口,还可以继承其他的类,实现其他的接口

2>增强了程序的扩展性,降低了程序的耦合性(解耦)

实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)

实现类中重写了run方法,用来设置线程任务

创建Thread类对象,调用start方法:用来开启新线程

(14)匿名内部类方式实现线程的创建

匿名内部类方式实现线程的创建:

匿名:没有名字

内部类:写在其他类内部的类

匿名内部类作用:简化代码

       把子类继承父类,重写父类的方法,创建子类对象合成一步完成

       把实现类实现接口,重写接口中的方法,创建实现类对象合成一步完成

匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

格式:

       new 父类/接口{

              重写父类/接口中的方法

}

9f6d0bd3b41743e3bdcd18a0cce20c32.png

3.线程同步机制

(1)线程安全问题的概述

3588898cfdce71901a19238d75f694fa.png

(2)线程安全问题的代码实现

a665f191a886b2942d29d03a6aeddee4.png

325c1e2c2b5c5a58003096b64fada5cd.png

(3)线程安全问题产生的原理

098e3d93d5e76cb40a7401f3b6f85ac3.png

(4)解决线程安全问题_同步代码块

卖票案例出现了线程安全问题,卖出了不存在的票和重复的票

解决线程安全问题的第一种方案:使用同步代码块

格式:

       synchronized(锁对象){

              可能会出现线程安全问题的代码(访问了共享数据的代码)

}

注意:

       1>同步代码块中的锁对象,可以使用任意的对象

       2>但是必须保证多个线程使用的锁对象是同一个

       3>锁对象的作用:

              把同步代码块锁住,只让一个线程在同步代码块中执行

(5)同步技术的原理

同步技术的原理:

使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器

3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法进行卖票

       t0抢到了cpu的执行权,执行run方法,遇到synchronized代码块

       这时t0会检查synchronized代码块中是否有锁对象

       发现有,就会获取到锁对象,进入到同步中执行

       t1抢到了cpu的执行权,执行run方法,遇到synchronized代码块

       这时t0会检查synchronized代码块中是否有锁对象

       发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象

       一直到t0线程执行完同步中的代码,会把锁对象归还给同步代码块

       t1才能获取到锁对象,进入到锁对象中执行

总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁也进不去同步代码块,所以同步保证了只能有一个线程在同步中执行共享数据,保证了线程的安全,程序频繁的判断锁,获取锁,释放锁,程序的效率会变低

(6)解决线程安全问题_同步方法

解决线程安全问题的第二种方案:使用同步方法

使用步骤:

       1>把访问了共享数据的代码抽取出来,放到一个方法中

       2>在方法上添加synchronized修饰符

格式:定义方法的格式

修饰符 synchronized 返回值类型方法名(参数列表){

       可能会出现线程安全问题的代码(访问了共享数据的代码)

}

(7)静态同步方法

b83c02474324a0777fb7d000e9f23f33.png

3192db31e30621dd5473d431367aca7d.png

(8)解决线程安全问题_Lock锁

 解决线程安全问题的第三种方案:使用Lock锁

java.util.concurrent.locks.Lock接口

Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作

Lock接口中的方法:

       void Lock():获取锁

       void unLock():释放锁

java.util.concurrent.locks.ReentrantLockimplements Lock接口

使用步骤:

       1>在成员位置创建一个ReentrantLock对象

       2>在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁

       3>在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

72b8c492ab1a25756ec09ba5a4b6fc70.png

46a0d5d9a7c82dd224a846e45b1549f3.png

4.等待唤醒机制

(1)线程状态概述

(2)等待唤醒案例分析

54b20e9275d9c4f637129b9ec3a7b173.png

Waiting(无限等待)状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。

5696d9034fd923ae4a79cf2024d1d3cf.png

(3)等待唤醒案例代码实现

等待唤醒案例:线程之间的通信

       创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃CPU的执行,进入到Waiting状态(无限等待)

       创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子

注意:

       顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行

       同步使用的所对象必须保证唯一

       只有其对象才能调用wait和notify方法

Object类中的方法

       void wait()

              在其他线程调用此对象的notify方法或notifyAll()方法前,导致当前线程等待

       void notify()

              唤醒在此对象监视器上等待的单个线程

              会继续执行wait方法之后的代码

918e616582b000e60a4a189d5a96a118.png

a5cc976670b3de3bc2ba6dd3d72b8782.png

(4)Object类中wait带参方法和notify

进入到TimeWaiting(计时等待)有两种方式

1>使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态

2>使用wait(long m)方法,wait方法如果在毫秒值结束之后还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

唤醒的方法:

       void notify():唤醒在此对象监视器上等待的单个线程

       void notifyAll():唤醒在此对象监视器上等待的所有线程

(5)线程间通信

d1f3b872cc2b355b56fefa5c0a810365.png

(6)等待唤醒机制概述

0875aa32c273351af39deea4a9b84a13.png

(7)等待唤醒机制需求分析

9cacb459e98a7459325de825565a277a.png

(8)等待唤醒机制代码实现_包子类&包子铺

2f6fd610e903e9ad3b7f6cde5fb045e8.png

872880c9537e5e4e4b5bbcc9934698a4.png

(9)等待唤醒机制代码实现_吃货类&测试

06c137ea6e3b255f1e929229380ed506.png

58e1a81470c680df0da8913437d9113e.png

5.线程池

(1)线程池的概念和原理

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程消耗过多的资源.

3df0a98c9a8373ca783dd38070e195fd.png

e4fe1098265c0c2004b2c0c6abac7f60.png

(2)线程池的代码实现

线程池:JDK1.5之后提供的

java.util.concurrent.Executors:线程池的工厂类,用来生产线程池

Executors类中的静态方法:

       static ExecutorServicenewFixedThreadPool(int nThreads):创建一个可重用固定线程数的线程池

       参数: int nThreads:创建线程池中包含的线程的数量

       返回值:ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)

java.util.concurrent.ExecutorService:线程池接口

       用来从线程池中获取线程,调用start方法,执行线程任务

              submit(Runnable task):提交一个Runnable任务用于执行

       关闭/销毁线程池的方法

              void shutdown()

线程池的使用步骤:

1>使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池

2>创建一个类,实现Runnable接口,重写run方法,设置线程任务

3>调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法

4>调用ExecutorService中的方法shutdown销毁线程(不建议执行)

注意:

线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用

6Lambda表达式

(1)函数式编程思想概述

面向对象的思想:

       做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.

函数式编程思想:

       只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,并不是过程.

(2)冗余的Runnable代码

88315b19a46580a83947d7b08eb8712e.png

e54c0e1e3dbc3fd5990d7eacab69b53d.png

代码分析:

对于Runnable的匿名内部类用法,可以分析出几点内容:

1>Thread类需要Runnable接口作为参数,其中的抽象run方法是用来指定线程任务内容的核心

2>为了指定run的方法体,不得不需要Runnable接口的实现类

3>为了省去定义一个RunnableImpl实现类的麻烦,不得不使用匿名内部类

4>必须覆盖重写抽象run方法,所以方法名称,方法参数,方法返回值不得不再写一遍,且不能写错.

5>而实际上,似乎只有方法体才是关键所在

(3)编程思想转换&体验Lambda的更优写法

7747c2d83b8702c4f0e5f04e4744fb34.png

(4)Lambda标准格式

一方面,匿名内部类可以帮我们省去实现类的定义,另一方面,匿名内部类的语法—确实太复杂了.

85996dfffd2040c29698a03514d0048c.png

Lambda表达式的标准格式:

由三部分组成:

       a.一些参数

       b.一个箭头

       c.一段代码

格式:

       (参数列表) -> {一些重写方法的代码}

解释说明格式:

       ():接口中抽象方法的参数列表,没有参数,就空着,有参数就写出参数,多个参数使用逗号分隔

       -> :传递的意思,把参数传递给方法体{}

       {}:重写接口的抽象方法方法体

(5)Lambda表达式的无参数无返回值的

7ffa6fe99f6dba724a07c7406f8277f5.png

6c3aafa3a1cbcd29c983dd53a567b88c.png

(6)Lambda表达式有参数有返回值的

f45595e1cbaaa807f80a41dae6d58813.png

(7)Lambda表达式有参数有返回值的练习

fd2be2b5dccf8a3b5ce260e7209c5d28.png

(8)Lambda省略格式&Lambda使用前

Lambda表达式:是可推导,可以省略

凡是根据上下文推导出来的内容,都可以省略书写

可以省略的内容:

       1>(参数列表):括号中参数列表的数据类型,可以省略不写

       2>(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略

       3>{一些代码}:如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)

       注意:要省略{},return,分号,必须一起省略

23c38359b9807e742fca25087de5a37d.png

6b2b2e9c32cee048e8de3e79307888cd.png

ca0e65c36641b7b07d92f97d26aab983.png

475cc19d0aa72253d87d7c183df0add7.png

Lambda的使用前提:

1>使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法

无论是JDK内置的Runnable,Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda

2>使用Lambda必须具有上下文推断

也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例

备注:有且仅有一个抽象方法的接口,称为”函数式接口”

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值