1.异常
(1)异常概念&异常体系
异常:指的是程序在执行过程中,出现非正常的情况,最终会导致JVM的非正常停止。
在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出一个异常的对象。Java处理异常的方式是中断处理。
异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行。
异常体系:
异常机制其实是帮助我们找到程序中的问题,异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Error与java.lang.Exception,平常所说的异常指的是java.lang.Exception
(2)异常分类
java.lang.Throwable:类是Java语言中所有错误或异常的超类
Exception:编译期异常,进行编译(写代码)Java程序出现的问题
RuntimeException:运行期异常,Java程序运行过程中出现的问题
异常就相当于程序得了一个小毛病,把异常处理掉,程序可以继续执行
Error:错误
错误就相当于程序得了一个无法治愈的毛病,必须修改源代码,程序才能继续执行
(3)异常的产生过程解析
(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
(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自己处理
(7)try_catch_异常处理的第二种方式
try…catch异常处理的第二种方式:
格式:
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接收try中抛出的异常对象){
异常的处理逻辑,异常异常对象之后,怎么处理异常对象
一般在工作中,会把异常的信息记录到一个日志中
}
…
catch(异常类名变量名){}
注意:
1>try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
2>如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完catch中的处理逻辑,继续执行try…catch之后的代码
如果try中没有产生异常,那么就不会执行catch中的异常处理逻辑,执行完try中的代码,继续执行try…catch之后的代码
(8)Throwable类中3个异常处理的方法
Throwable类中定义了三个异常处理的方法:
StringgetMessage():返回此throwable的简短描述
String toString():返回此throwable的详细消息字符串
voidprintStackTrace():JVM打印异常对象默认此方法,打印的异常信息是最全面的
(9)finally代码块
finally代码块
格式:
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接收try中抛出的异常对象){
异常的处理逻辑,异常异常对象之后,怎么处理异常对象
一般在工作中,会把异常的信息记录到一个日志中
}
…
catch(异常类名变量名){
}
finally{
无论是否出现异常都会执行
}
注意:
1>finally不能单独使用,必须和try一起使用
2>finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要释放资源(IO)
(10)异常注意事项_多异常的捕获处理
异常的注意事项:
多个异常使用捕获又该如何处理?
1>多个异常分别处理
2>多个异常一次捕获,多次处理
一个try多个catch注意事项:
catch里面定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上面,否则就会报错
ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException
3>多个异常一次捕获,一次处理
运行时异常被抛出可以不处理,即不捕获也不声明抛出。
默认给虚拟机处理,终止程序,什么时候不抛出运行时异常了,再来继续执行异常
(11)异常注意事项_finally有return语句
(12)异常注意事项_子父类异常
子父类的异常:
-如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者父类异常的子类或者不抛出异常
-父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常,只能捕获处理,不能声明抛出
注意:父类异常是什么样,子类异常就什么样
(13)自定义异常类
自定义异常类:
Java提供的异常类,不够我们使用,需要自己定义一些异常类
格式:
public class XXXException extendsException | RuntimeException{
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}
注意:
1>自定义异常类一般都是以Exception结尾,说明该类是一个异常类
2>自定义异常类,必须继承Exception或者RuntimeException
继承Exception:那么自定义的异常类就是一个编译器异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try…catch
继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
(14)自定义异常类的练习
要求:模拟注册操作,如果用户名已经存在,则抛出异常并提示:亲,该用户名已经被注册
分析:
1>使用数组保存已经注册过的用户名(数据库)
2>使用Scanner获取用户输入的注册的用户名(前端,页面)
3>定义一个方法,对用户输入过的用户名进行判断
遍历存储已经注册过的用户名的数组,获取每一个用户名
使用获取到的用户名和用户输入的用户名比较
true:用户名已经存在,抛出RegisterException异常,告知用户”亲,该用户名已经被注册了!”
false:继续遍历比较
如果循环结束了,还没有找到重复的用户名,提示用户”恭喜您,注册成功!”
2.线程实现方式
(1)并发与并行
并发:指两个或多个事件在同一时间段内发生
并行:指两个或多个时间在同一时刻发生(同时发生)
(2)进程概念
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建,运行到消亡的过程。
(3)线程概念
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称为多进程程序。
简而言之:一个程序运行后之后有一个进程,一个进程中可以包含多个线程
(4)线程调度
分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调用。
(5)主线程
主线程:执行主(main)方法的线程
单线程程序:Java学习中只有一个线程
执行从main方法开始,从上到下依次执行
JVM执行main方法,main方法会进入到栈内存
JVM会找操作系统开辟一条main方法通向cpu的执行路径
cpu就可以通过这个路径来执行main方法
而这个路径有一个名字,叫main(主)线程
(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)多线程原理_随机性打印结果
(8)多线程原理_多线程内存图解
(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):使当前正在执行的线程以指定的毫秒值进行暂停(暂时停止执行)
毫秒数结束之后,线程继续执行。
(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 父类/接口{
重写父类/接口中的方法
}
3.线程同步机制
(1)线程安全问题的概述
(2)线程安全问题的代码实现
(3)线程安全问题产生的原理
(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)静态同步方法
(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释放锁
4.等待唤醒机制
(1)线程状态概述
(2)等待唤醒案例分析
Waiting(无限等待)状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
(3)等待唤醒案例代码实现
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃CPU的执行,进入到Waiting状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
同步使用的所对象必须保证唯一
只有其对象才能调用wait和notify方法
Object类中的方法
void wait()
在其他线程调用此对象的notify方法或notifyAll()方法前,导致当前线程等待
void notify()
唤醒在此对象监视器上等待的单个线程
会继续执行wait方法之后的代码
(4)Object类中wait带参方法和notify
进入到TimeWaiting(计时等待)有两种方式
1>使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2>使用wait(long m)方法,wait方法如果在毫秒值结束之后还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
唤醒的方法:
void notify():唤醒在此对象监视器上等待的单个线程
void notifyAll():唤醒在此对象监视器上等待的所有线程
(5)线程间通信
(6)等待唤醒机制概述
(7)等待唤醒机制需求分析
(8)等待唤醒机制代码实现_包子类&包子铺
(9)等待唤醒机制代码实现_吃货类&测试
5.线程池
(1)线程池的概念和原理
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程消耗过多的资源.
(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销毁线程(不建议执行)
注意:
线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
6.Lambda表达式
(1)函数式编程思想概述
面向对象的思想:
做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.
函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,并不是过程.
(2)冗余的Runnable代码
代码分析:
对于Runnable的匿名内部类用法,可以分析出几点内容:
1>Thread类需要Runnable接口作为参数,其中的抽象run方法是用来指定线程任务内容的核心
2>为了指定run的方法体,不得不需要Runnable接口的实现类
3>为了省去定义一个RunnableImpl实现类的麻烦,不得不使用匿名内部类
4>必须覆盖重写抽象run方法,所以方法名称,方法参数,方法返回值不得不再写一遍,且不能写错.
5>而实际上,似乎只有方法体才是关键所在
(3)编程思想转换&体验Lambda的更优写法
(4)Lambda标准格式
一方面,匿名内部类可以帮我们省去实现类的定义,另一方面,匿名内部类的语法—确实太复杂了.
Lambda表达式的标准格式:
由三部分组成:
a.一些参数
b.一个箭头
c.一段代码
格式:
(参数列表) -> {一些重写方法的代码}
解释说明格式:
():接口中抽象方法的参数列表,没有参数,就空着,有参数就写出参数,多个参数使用逗号分隔
-> :传递的意思,把参数传递给方法体{}
{}:重写接口的抽象方法方法体
(5)Lambda表达式的无参数无返回值的
(6)Lambda表达式有参数有返回值的
(7)Lambda表达式有参数有返回值的练习
(8)Lambda省略格式&Lambda使用前
Lambda表达式:是可推导,可以省略
凡是根据上下文推导出来的内容,都可以省略书写
可以省略的内容:
1>(参数列表):括号中参数列表的数据类型,可以省略不写
2>(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略
3>{一些代码}:如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)
注意:要省略{},return,分号,必须一起省略
Lambda的使用前提:
1>使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法
无论是JDK内置的Runnable,Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda
2>使用Lambda必须具有上下文推断
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例
备注:有且仅有一个抽象方法的接口,称为”函数式接口”