第七章 异常
1. 异常概念
-
异常概念
- 异常是一个类,处理异常:中断处理。
- 异常不是语法错误,语法错误编译不通过。
-
异常体系
- 异常的所有父类是
java.lang.throwable
,其下有两个子类java.lang.Error
和java.lang.Exception
,平常所说的异常是后者。- error是非常严重的问题,叫
错误
,只能规避,无法处理,尽量避免; - exception,可以处理。
- 异常分类
exception
编译期异常,写代码时异常。- RuntimeException:运行期异常,java运行过程中出现的问题。
Error
:错误,必须修改代码,程序才能运行。
- 异常产生过程解析:
- 方法创建异常对象(名称,原因,位置)–>抛出给main方法,在抛出给–>控制台。
-
2. 异常的处理
2.1抛出异常 throw
throw
关键字
-
使用throw在指定的方法中抛出指定异常
-
使用格式:
throw new xxxException("异常产生的原因")
-
throw关键字必须写在方法的内部
-
throw关键字后new的对象必须是Exception或者Exception的子类对象。
-
throw关键字抛出指定的异常对象,我们就必须处理这个异常对象。
throw关键字后面创建的是RuntimeException或者它的子类对象可以不处理,交给JVM处理
throw后创建的是编译异常(写代码时的异常),则必须处理这个异常,要么throws,要么try…catch
-
必须对方法传递到参数进行合法性校验,如果参数不合法,必须告知调用者传递的参数有问题。
2.2Objects非空判断
对传递参数的合法性判断,判断是否为null,
Objects.requireNonNull(obj,"传递的时空null");
2.3声明异常 throws
-
异常处理的第一种方法:交给别人处理。
-
作用:方法内部抛出异常时,就必须处理这个异常对象。最终交给JVM处理。
-
使用格式:在方法声明时使用
修饰符 返回值类型 方法名(参数裂变) throws xxxException, xxxException{
throw new XXXException(“产生原因”);
}
-
throws关键字必须写在主方法声明处,
-
throws关键字后边声明的异常必须是Exception或者Exception的子类
-
方法内部抛出多个异常对象,那么throws后边也必须声明多个异常;
方法内部如果抛出了多个异常对象有子父类关系,那么直接声明父类异常即可。
-
调用了一个声明抛出异常的方法,就必须处理声明的异常。
要么使用throws声明抛出,交给方法调用者,最终交给JVM
要么try…catch自己处理异常
-
2.4捕获异常try…catch
-
异常处理的第二种方法:自己处理。
-
try{
可能产生异常的代码;
}catch(xxException e){
异常的处理逻辑;一般工作中会记录到日志中。
}catch(){
…}
-
try中可能抛出多个异常对象,那么可以使用多个catch来处理这些异常对象。
-
如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕之后,继续执行catch之后的代码
-
如果try中没有异常,那么就不会执行catch中异常的处理逻辑,执行完try中的代码,继续执行try…catch之后的代码。
-
-
throwable
getMessage()
返回此 throwable 的简短描述。toString()
返回此 throwable 的详细消息字符串。printStackTrace()
JVM默认此方法,打印异常信息最全面。
2.5 finally代码块
try…catch中,try异常后的代码块不执行,catch后的代码执行。
finally
中的代码块无论是否出现异常,一定可以执行- finally不能单独使用,必须和try一起使用
- finally一般用于资源释放/回收,无论程序是否出现异常,都要进行资源释放发。
2.6异常注意事项
多个异常使用捕获该如何处理?
-
多个异常分别处理。每个异常一个try…catch
-
多个异常一次捕获,多次处理。一个try,多个catch.(如果多个异常有子父类关系,catch中子类异常必须写在前面)
-
多个异常一次捕获一次处理。 一个try…一个catch(一个Exception全部处理)。
-
运行时异常可以不处理,默认给虚拟机处理。
如果finally中有return语句,永远返回finally中的结果,应避免该情况。
-
如果父类抛出多个异常,子类重写父类该方法时,抛出 和父类相同的异常 / 父类异常的子类 / 不抛出异常
-
父类方法没有抛出异常,子类重写父类方法时不可抛出异常。此时子类产生异常,只捕获,不能声明抛出。
【父类异常什么样,子类一场就什么样。】
3. 自定义异常
-
概述
-
java提供的异常类不够用,自己自定义一些异常类。
-
格式:public class xxxException extends Exception / RuntimeExcetion{ 添加空参数构造方法;public xxxException(){ }//空参构造 添加带构造信息构造方法;public xxxException(String str){ super(str); }//调用父类方法解决异常 } 注意:1.自定义异常类一般都是以Exception结尾,说明该类是一个异常类。 2.自定义异常类,必须的继承Exception或者RuntimeException 继承exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try...catch 继承RuntimeException:那么自定义的异常类就是一个运行异常。无需处理,交给虚拟机处理。中断处理。
-
-
自定义异常练习
要求:模拟注册操作,如果用户名已经存在,则抛出异常并提示:亲,该用户名已存在
分析:
-
使用数组保存已经注册过用户名(数据库)
-
使用scanner获取用户输入的注册的用户名(前端,页面)
-
定义一个方法,对用户输入的注册的用户名进行判断
遍历存储到已经注册过用户名的数组,获取每一个用户名
使用获取到的用户名和用户输入的用户比较
true:用户名已存在,抛出RegisterException异常,告知用户“亲,该用户已经被注册”。
false:继续遍历比较。循环结束,提示 ”恭喜注册成功“。
-
第八章 多线程
1线程类
-
并发与并行
- 并发:交替执行。单核单线程
- 并行:同时执行,
-
线程与进程
- 进程:一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;
- 线程:线程是进程执行中的一个执行单元,负责当前进程中的程序的执行,一个进程至少有一个线程;一个进程可以有多个线程。一个程序运行至少有一个进程,一个进程可以包含多个线程。
- 线程调度
- 分时调度:所有线程轮流使用CPU的使用权,平均分配给每个线程占用时间。
- 抢占调度:优先级高的线程先使用CPU,优先级相同,随机选择。JAVA使用抢占调度。
-
创建线程类
-
主线程:执行主(main)方法的线程。
-
单线程程序:java程序中只有一个线程。执行main方法开始,从上到下依次执行。
-
多线程创建方式一:创建Thread类的子类
-
java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类。
-
实现步骤:
1.创建一个Thread类 2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?) 3.创建Thread类的子类对象 4.调用Thread类中的start方法,开启新的线程,执行run方法 void start() 使该线程开始执行;java虚拟机调用该线程的run方法 结果是两个线程并发的运行;当前线程(从调用返回给start方法)和另一个线程(执行其run方法)。 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
-
-
2多线程
-
多线程原理
-
Thread类:
-
Thread类构造方法:
public Thread()
:分配一个新的线程对象。public Thread(String namr)
:分配一个指定名字的线程对象。public Thread(Runnable target)
:分配一个带有指定目标新的线程对象。public Thread(Runnable target, String name)
:分配一个带有指定目标的线程对象并指定名字。
-
Thread常用方法:
-
public String getName()
:获取当前线程的名字。获取线程的名称:在run方法中
- 直接使用Thread类中的方法getName()
String getName()
返回该线程的名称。 - 先获取当前在执行的线程
static Thread currentThread()
,使用线程的.getName()
获取线程的名称。
设置线程中的名称:
- 直接使用Thread类中的方法
void setName(String name)
设置线程名称。 - 创建一个带参构造方法,参数传递线程名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程
- 直接使用Thread类中的方法getName()
-
public void start()
:此线程开始执行;Java虚拟机调用用此线程的run方法。 -
public void run()
:此线程要执行的任务在此处定义代码。 -
public static void sleep(long milllis)
:使当前正在执行的线程暂停毫秒数值。可以让主程序暂停执行。“秒表打印”。
-
public static Thread currenThread()
:返回当前正在执行的线程对象的引用。
-
-
-
多线程创建方式二:
- 创建runnable接口的实现类,然后实现方法。
- 创建一个Runnable接口的实现类;在其中重写run方法,设置线程任务。
- 创建一个Runnable实现类的对象。
- 创建一个Thread类对象,构造方法中传递Runnable接口的实现类对象。
- 调用Thread中的方法,开启新的线程执行run方法。
- 创建runnable接口的实现类,然后实现方法。
-
Thread和Runnable的区别:
-
实现Runnable接口创建多线程的好处
-
一个类只能继承一个类,不能继承其他类。实现了Runnable接口,还可以继承其他类,实现其他接口。
-
在增强程序扩展性,降低程序耦合性(解耦)。
实现Runnable接口的方式,把设置线程任务和开启新线程进行分离(解耦)。
实现类中,重写run方法: 就是用来设置线程任务。
创建Thread类对象,调用start方法:开启新线程。
-
-
-
匿名内部类:继承/实现;重写;创建子类;一步完成这三个操作。
格式:
new Thread(){
重写run方法
}.start(); //线程的父类
Runnable r = new Runnable(){
重写run方法
};//实现runnable接口
new Thread(r).start();
3线程安全
-
线程安全
多线程访问共享数据会发生安全问题。
保证线程安全:一个线程在访问共享数据时,其他线程只能等待。保证:始终只有一个线程在卖票。
-
线程同步:解决线程安全问题。
- 有3种方法:同步代码块;同步方法;lock锁机制。
-
同步代码块
synchronize(锁对象){ 访问了共享数据的代码/可能出现安全问题的代码 } 注意:通过代码块中的锁对象,可以使用任意的对象。 必须保证多个线程使用的锁对象相同。 锁对象作用:吧代码块锁住,只让一个线程在同步代块中执行。
使用方法:将代码块写进Runnable实现类的run方法中。
-
同步方法
public synchronized void method(){ 可能会产生安全问题的代码块}
:使用步骤:
- 把访问了共享数据的方法抽取出来,放到加了sunchronized修饰符的方法中。
- 在实现类的方法中调用该
method
方法。
同步方法也会把方法内部代码锁住,只让一个线程执行。
同步方法的锁对象就是 this。
【静态同步方法:】
- 在同步方法前和使用的数据前加上static。也能保证线程安全。
- 静态方法的锁对象:本类的class属性/class文件对象。
-
lock机制(用的多)
-
lock锁
java.util.current.locks
-
Lock接口种两个重要方法:
lock
和unlock
- 在成员位置创建ReentrantLock对象(Lock接口的实现类对象)。
- 可能出现安全问题的代码前,调用Lock接口中Lock方法,获取锁。
- 在可能出现安全问题的额代码后调用Lock接口中的方法unlck释放锁。
改进:将安全问题代码写进try中,将l.unlock()写进finally代码块中,无论是否出现异常都会释放锁。
-
##4线程状态
-
线程状态概述
线程状态 导致状态发生的条件 New(新建) 线程刚被创建,但是并未启动。还没调用start方法。 Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己的代码,也可能没有,取决于操作系统处理器 Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变为Runnable状态。 Waiting(无限等待) 一个线程等待另一个线程执行一个动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法唤醒。 Timed Wainting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Wainting状态。这一状态将一直保持到超时期满或者收到唤醒通知。带有超时参数的常用方法有Thread.sleep,Object.wait。 Teminated(被终止) run方法正常运行结束而消亡;或者因为没有捕获的异常终止了方法而死亡。 -
等待唤醒案例: 线程之间的通信。
-
创建第一个线程,调用wait方法 ,进入wainting(无限等待)状态。
-
创建第二个线程,执行结束后调用notify方法,唤醒第一个线程执行。
- 两个线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行。
- 两个进程必须使用同一个锁对象。
- 只有锁对象才能调用wait和notify方法。
-
void wait()
:其他线程调用notify或notifyAll方法前,导致当前线程等待。void notify()
:唤醒在此监视器上的等待的单个线程;会继续执行wait方法之后的代码。
-
-
-
Timed Waiting(计时等待)
- 使用sleep(long m)方法后,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态。
- 使用
wait(long m)
方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,进入到Runnable/Blocked状态。 notifyAll()
:如果有多个等待线程,唤醒所有的等待线程。
-
BLOCKED(锁阻塞)
-
Waiting(无限等待)
第九章 等待与唤醒机制
1 等待唤醒机制
-
线程间通信
-
等待唤醒机制
wait():
notify():唤醒等待时间最久的线程。
notifyAll():唤醒等待线上所有的等待的线程。
唤醒之后也不是立即就可以执行,进入Blocked状态的话还是需要争夺CPU使用权;进入Runnable状态的话就可以执行了。
-
调用wait与notify方法注意的细节。
- wait方法和Notify方法必须要由同一个锁对象调用。因为:同一个锁通过notify唤醒同一个锁对象调用的wait方法。
- wait方法和notify方法是属于Object方法的。因为:锁对象任意,任意对象所属类都继承了Object类。
- wait方法和notify方法必须要在同步代码块或者同步函数中使用。因为:必须通过锁对象调用这两个方法。
1.3生产者与消费者问题
A:生产者线程类(包子铺)。
- 是一个线程类,可以继承Thread。线程任务:生产包子
- 对包子状态判断:有包子等待;无包子生产(交替生产两种不同的包子)
- 生产包子以后修改包子状态,唤醒吃货线程。
- 成员位置定义包子变量,带参构造为包子变量赋值。
B:消费者线程类(吃货):线程类。
- A和B通信。
- A和B必须同时同步技术保证两个线程只有一个在执行。
- 锁对象必须保证唯一,可以使用包子对象作为锁对象。
- 包子铺类和吃货的类需要把包子作为参数传递进来。
C:包子类:皮,馅,状态。
D:测试线程:创建包子对象;创建包子铺(包子)对象.start;创建吃货(包子)对象.start。
2线程池
2.1线程池概念
线程池:容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
使用线程池的好处:
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性。
2.2线程池的使用
java.util.concurrent.Executors
:生产线程池的工厂类。(jdk1.5之后提供的)
static ExecutorService newFixedThreadPool(int nThreads)
:创建一个可重用固定线程数的线程池。
返回值:
- ExecutorService接口,返回的是ExecutorSercive接口的实现类对象,可以使用ExecutorSercive接口接收。
java.util.concurrent.ExecutorService
:线程池接口
submit(Runnable task)
:从线程池中获取线程,调用start方法,实行线程任务。提交一个Runnable任务用于执行。void shutdown()
:关闭/销毁线程池。
线程池持使用步骤:
- 使用线程池的工厂类
Executors
里边提供的静态方法newFixedThreadPool
生产一个之当线程数量的线程池。 - 创建一个类,实现Runnabl接口,重写run方法,设置线程任务。
- 调用
ExcutorService
中的方法submit
,传递线程任务(实现类),开启线程,执行run方法。 - 调用ExecutorService中的方法shutdown销毁线程池(不建议执行,销毁了就少了一个线程了。)
3Lambda表达式
3.1函数式编程思想概述
-
面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成任务。强调通过对象的形式来做。
函数时编程思想:强调做什么,而不是以什么形式做。只要能获取到结果就行,重视结果,不重视过程。
-
冗余的Runnable代码
-
编程思想转换:体验Lambda
-
new Thread(()->{ System.out.println(Thread.currentThread().getName()+"创建了一个新线程"); }).start();
3.4体验Lambda的更优化写法
3.5匿名内部类回顾
3.6Lamda标准格式
-
一些参数
-
一个箭头
-
一段代码
-
标准格式:
(参数类型 参数名称) -> { 代码语句/重写方法的代码 } ():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔。 ->:把参数传递给方法体{}。 {}:重写接口的抽象方法。
-
Lambda的参数和返回值
-
Lambda标准模式(有参数有返回)
-
练习:使用Lambda省略格式
可推导可省略。
可省略的内容跟:
- (参数列表):括号中参数列表的数据类型,可以省略不写。
- (参数列表):括号中的参数如果只有一个,那么类型和括号都可以省略。
- {一些代码}:如果大括号中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)。要省略,这三个要一起省略。
-
Lambda使用前提:
- 使用Lambda必须具有接口,且要求接口中有且只有一个抽象方法。
- 使用Lambda必须具有上下文推断。方法的参数或者局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
【备注:】有且仅有一个抽象方法的接口,称为“函数式接口”。
-