第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式

 

第一节、异常

 

一、异常

(一)异常的概念、特点

1、概念:

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

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

(2)异常对象:在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。

(3)中断处理:Java处理异常的方式是中断处理。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

2、特点:异常体系(Throwable体系)

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(1)Error:严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。

(2)Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒、阑尾炎。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(二)异常的分类

1、编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)

2、运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(三)Throwable中的常用方法:    

1、void printStackTrace()`:打印异常的详细信息。(包含了异常的类型、原因、位置)

2、String getMessage()`:获取发生异常的原因。

3、String toString()`:获取异常的类型和异常描述信息(不用)。

 

二、异常的处理(throw、throws、try-catch-finally、Object)

(一)throw抛出异常

1、概念: throw是语句抛出一个异常,使用的不多,一般是在代码块的内部,当程序出现某种逻辑错误时由程序员主动抛出某种特定类型的异常。

 

2、格式:throw new 异常类名(参数);

(1)创建一个异常对象。封装一些提示信息(信息可自己编写)。

(2)将这个异常对象告知给调用者。à通过关键字throw就可以完成。throw new异常对象。

3、特点 :

(1)throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。

(2)注意:如果产生了问题,我们就会throw将问题描述类即异常进行抛出,也就是将问题返回给该方法的调用者。

(3)对于调用者该怎么处理呢?一是进行捕获处理,二是继续将问题使用throws声明处理。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(二)、throws声明异常

1、概念:

(1)将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理,那么必须通过throws进行声明,让调用者去处理。

(2)关键字throws运用于方法声明之上,用于表示当前方法不处理异常,提醒该方法的调用者来处理异常(抛出异常)。

2、格式: 修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{   }

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(三)、try-catch-finally捕获异常

1、概念

(1)如果异常出现的话,会立刻终止程序,所以我们得处理异常:

①该方法不处理,而是声明抛出,由该方法的调用者来处理(throws)。

②在方法中使用try-catch的语句块来处理异常。

(2)try-catch的方式就是捕获异常。捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。

(3)try:该代码块中编写可能产生异常的代码。  catch:用来进行某种异常的捕获,实现对捕获到的异常进行处理。(注意:try和catch都不能单独使用,必须连用。)在finally代码块中存放的代码都是一定会被执行的。(除非try或者catch中调用退出JVM方法)

2、格式:

try{

     编写可能会出现异常的代码

}catch(异常类型  e){

     处理异常的代码

     //记录日志/打印异常信息/继续抛出异常

} finally{

       语句体

}

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(四)Objects非空判断

1、概念:

类Objects由一些静态的方法组成,是null-save(空指针安全的)或null-tolerant(容忍空指针的),在它的源码中,对对象为null的值进行了抛出异常操作。

2、格式:public static <T> T requireNonNull(T obj)`:查看指定引用对象不是null。

3、源码:如下图

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记---

(五)如何获取异常信息(Throwable类的方法)

1、String getMessage()`:获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。

2、String toString()`:获取异常的类型和异常描述信息(不用)。

3、void printStackTrace()`:打印异常的跟踪栈信息并输出到控制台。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记
(六)、异常的注意

1、多个异常使用捕获的处理:一次捕获多次处理

注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

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

3、如果finally有return语句,永远返回finally中的结果,避免该情况。

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

5、父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(七)异常异常的比较与编程习惯

1、throw与throws的比较:

①throws出现在方法函数头;而throw出现在函数体。

②throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。

③两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

2、throws和try-catch的比较:

①对于throws关键字而言一般用于抛出编译时的异常。

如果某个异常是编译是异常,那么这个异常就需要捕获(try{}catch{}finally{})或者抛出(throws),否者的话是无法通过编译的。

②主要区别:

---对于try{}catch{}finally{}而言,相应的异常已经在程序里面捕获。相应的catch中也已经处理了相关的异常,不会再向上抛出。上层调用者是不需要进行任何处理的。

---对于throws而言,当前程序不会对异常进行任何处理,如果出现异常的话,仅仅抛出而不做处理,对应的处理需要上层来负责。对应的上层调用者必须处理这个异常或者继续向上抛出对应的异常。

3、编程习惯

①在写程序时,对可能会出现异常的部分通常要用try{…}catch{…}去捕捉它并对它进行处理;

②用try{…}catch{…}捕捉了异常之后一定要对在catch{…}中对其进行处理,那怕是最简单的一句输出语句,或栈输入e.printStackTrace();

③如果是捕捉IO输入输出流中的异常,一定要在try{…}catch{…}后加finally{…}把输入输出流关闭;

④如果在函数体内用throw抛出了某种异常,最好要在函数名中加throws抛异常声明,然后交给调用它的上层函数进行处理。

 

三、自定义异常

(一)概念:

1、在开发中根据自己业务的异常情况来定义异常类:

Java中不同的异常类,分别表示着某一种具体的异常情况。在开发中有些异常情况是SUN公司没有定义的,此时根据自己业务的异常情况来定义异常类。(例如年龄负数问题,考试成绩负数问题等等。)

2、自定义一个业务逻辑异常: **RegisterException**。一个注册异常类。

(二)格式:

1、自定义一个编译期异常: 自定义类 并继承于java.lang.Exception。

2、自定义一个运行时期的异常类: 自定义类 并继承于java.lang.RuntimeException。

(三)自定义异常的练习:

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

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第二节、多线程  【多线程、线程安全、线程状态、等待唤醒机制、线程池】

 

一、多线程

 

(一)多线程的概念

1、并发与并行

(1)并行:指两个或多个事件在同一时刻发生(同时发生)。

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

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

2、进程与线程

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

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

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

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

3、线程调度

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

(2)抢占式调度:

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

②抢占式调度详解:大部分操作系统都支持多进程并发运行,支持同时运行多个程序。比如:一边用编辑器,一用录屏软件,开着画图,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。

实际上,CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对要快,看上去就是在同一时刻运行。

③其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

 

(二)多线程原理

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

1、程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。

随着调用mt的对象的 start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。

2、多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。

当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

 

(三)Thread类(创建线程之继承Thread)

1、概念:

(1)创建线程的方式有两种,继承Thread类,实现Runnable接口。

(2)使用`java.lang.Thread`类代表线程,所有的线程对象都必须是Thread类或其子类的实例。

(3)每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。

2、格式:

(1)定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。

(2)创建Thread子类的实例,即创建了线程对象。

(3)调用线程对象的start()方法来启动该线程。

3、方法:

(1)构造方法

①Thread() :分配一个新的线程对象。

②Thread(String name) :分配一个指定名字的新的线程对象。

③Thread(Runnable target) :分配一个带有指定目标新的线程对象。

④Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(2)常用方法

①String getName() :返回当前线程名称。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

②void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。

③void run() :此线程要执行的任务在此处定义代码。

④static void sleep(long millis):使当前正在执行的线程暂停几秒钟(暂时停止执行)。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

⑤static Thread currentThread() :返回对当前正在执行的线程对象

 

(四)Runnable接口(创建线程之实现Runnable)

1、概念:

(1)通过实现Runnable接口,使得该类有了多线程类的特征。

run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。

(2)Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread 对象的start()方法来运行多线程代码。

2、格式:

(1)定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

(2)创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

(3)调用线程对象的start()方法来启动线程。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

3、特点:

(1)实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。

因此,不管是继承Thread类还是实现 Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

(2)Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。

而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

4、Thread和Runnable的区别:

(1)实现Runnable接口比继承Thread类的优势:

①适合多个相同的程序代码的线程去共享同一个资源。

②可以避免java中的单继承的局限性。

③增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

④线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

(2)注意:

①一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

②在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。

因为每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。

 

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

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

 

二、同步-线程安全

(一)概念

1、线程安全:

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(1)当使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题:

几个窗口(线程)票数不同步了,这种问题称为线程不安全。

(2)线程安全问题都是由全局变量及静态变量引起的:

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般这个全局变量是线程安全的;

若有多个线程同时执行写操作,一般都需要考虑线程同步, 否则的话就可能影响线程安全。

2、线程同步:保证每个线程都能正常执行原子操作,Java引入了线程同步机制(同步代码块、同步方法、锁)。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(二)线程同步机制(同步代码块、同步方法、锁)

1、同步代码块。

①概念:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

②格式:

synchronized(同步锁){

需要同步操作的代码

}

其中的同步锁只是一个概念,可以想象为在对象上标记了一个锁。

- 锁对象可以是任意类型。

- 多个线程对象要使用同一把锁。

- 任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

2、同步方法。

①概念:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

②格式:

public synchronized void method(){

可能会产生线程安全问题的代码

}

其中的同步锁是什么?

- 对于非static方法,同步锁就是this。

- 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

3、锁机制--Lock锁

①概念:

java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,

同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。Lock锁也称同步锁,加锁与释放锁方法化了

②常用方法:

- void lock(): 加同步锁。

- void unlock(): 释放同步锁。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

 

三、线程状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)

(一)六种线程状态概述:

1、初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

2、运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。

线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

3、阻塞(BLOCKED):表示线程阻塞于锁。

4、等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

5、计时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

6、终止(TERMINATED):表示该线程已经执行完毕。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

 

(一)NEW (初始):

顾名思义,这个状态,只存在于线程刚创建,未start之前:

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(二)RUNNABLE (运行):

这个状态的线程,其正在JVM中执行,但是这个"执行",不一定是真的在运行,也有可能是在等待CPU资源。

所以,把这个状态区分为READY和RUNNING两个,READY表示start了且资源一到位随时可以执行,RUNNING表示真正的执行中

Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。

线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(三)BLOCKED (阻塞):表示线程阻塞于锁。

线程等待获取一个锁,来继续执行下一步的操作,比较经典的就是synchronized关键字,这个关键字修饰的代码块或者方法,均需要获取到对应的锁,在未获取之前,其线程的状态就一直未BLOCKED,如果线程长时间处于这种状态下,我们就是当心看是否出现死锁的问题了。

一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。

(比如,线程A与线程B代码中使用同一锁,如果线程A获 取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。)

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(四)WAITING(等待):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

一个线程进入这个状态,一定是执行了如下的一些代码,例如

Object.wait()

Thread.join()

LockSupport.park()

当一个线程执行了Object.wait()的时候,它一定在等待另一个线程执行Object.notify()或者Object.notifyAll()。

或者一个线程thread,其在主线程中被执行了thread.join()的时候,主线程即会等待该线程执行完成。

当一个线程执行了LockSupport.park()的时候,其在等待执行LockSupport.unpark(thread)。当该线程处于这种等待的时候,其状态即为WAITING。需要关注的是,这边的等待是没有时间限制的,当发现有这种状态的线程的时候,若其长时间处于这种状态,也需要关注下程序内部有无逻辑异常。

一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。waiting状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系,多个线程会争取锁,同时相互之间又存在协作关系。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

1、Object.wait()

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

2、Thread.join()

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

3、LockSupport.park()

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(五)TIMED_WAITING (计时等待):该状态不同于WAITING,它可以在指定的时间后自行返回。

1、概念:

WAITING状态等待的时间是永久的,即必须等到某个条件符合才能继续往下走,否则线程不会被唤醒。但是TIMED_WAITING,等待一段时间之后,会唤醒线程去重新获取锁。当执行如下代码的时候,对应的线程会进入到TIMED_WAITING状态。

Thread.sleep(long)

Object.wait(long)

Thread.join(long)

LockSupport.parkNanos()

LockSupport.parkUntil()

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

2、sleep方法特点:

(1)进入TIMED_WAITING状态的一种常见情形是调用的sleep方法,单独的线程也可以调用,不一定非要有协作关系。

(2)为了让其他线程有机会执行,可以将Thread.sleep()的调用放到线程run()之内。这样才能保证该线程执行过程中会睡眠

(3)sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。

(4)sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始立刻执行。

3、TIMED_WAITING与WAITING:联系紧密。

(1)比如Waiting(无限等待) 状态中wait方法是空参的,而timed waiting(计时等待) 中wait方法是带参的。

这种带参的方法,其实是一种倒计时操作,相当于我们生活中的小闹钟,我们设定好时间,到时通知,可是如果提前得到(唤醒)通知,那么设定好时间在通知也就显得多此一举了,那么这种设计方案其实是一举两得。

(2)如果没有得到(唤醒)通知,那么线程就处于Timed Waiting状态,直到倒计时完毕自动醒来;如果在倒计时期间得到(唤醒)通知,那么线程从Timed Waiting状态立刻唤醒。

4、实例:

实现一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

①Thread.sleep(long)

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

②Object.wait(long)

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(六)TERMINATED (终止):表示该线程已经执行完毕。

 

 

四、等待唤醒机制(解决线程间通信)

(一)概念

1、线程间通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

2、等待唤醒机制:多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。

3、wait/notify协作机制:一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时,如果需要,可以使用notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。

(二)方法

1、wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set中释放出来,重新进入到调度队列(ready queue)中。

2、notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。

3、notifyAll:则释放所通知对象的 wait set 上的全部线程。

(三)特点:

1、wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。

2、wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。

3、wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

4、哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行:

- 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;

- 否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

(四)实例:生产者与消费者问题

等待唤醒机制其实就是经典的“生产者与消费者”的问题。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

 

五、线程池

(一)线程池的概念:

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

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(二)合理利用线程池的好处:

1、降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2、提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

3、提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

(三)线程池的使用

静态工厂方法:

Executors类中:

static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。

(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

 

获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:

Future<?> submit(Runnable task): 获取线程池中的某一个线程对象,并执行

Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。

 

/*

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

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

    Executors类中的静态方法:

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

        参数:

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

        返回值:

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

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

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

              Future<?> submit(Runnable task) 提交一个 Runnable 任务用于执行

        关闭/销毁线程池的方法

            void shutdown()

    线程池的使用步骤:

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

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

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

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

 */

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

 

 

 

 

 

第三节、Lambda表达式

一、函数式编程思想概述

(一)面向对象思想:

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

(二)函数式编程思想:

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

二、冗余的Runnable代码

(一)传统写法

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(二)代码分析

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

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

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

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

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

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

三、编程思想转换

我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而**不得不**创建一个对象。我们真正希望做的事情是:将`run`方法体内的代码传递给`Thread`类知晓。

传递一段代码**——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。

四、体验Lambda的更优写法

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

五、回顾匿名内部类

(一)使用实现类

要启动一个线程,需要创建一个`Thread`类的对象并调用`start`方法。

而为了指定线程执行的内容,需要调用`Thread`类的构造方法:public Thread(Runnable target)`

为了获取`Runnable`接口的实现对象,可以为该接口定义一个实现类`RunnableImpl,然后创建该实现类的对象作为`Thread`类的构造参数。

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(二)使用匿名内部类

这个`RunnableImpl`类只是为了实现`Runnable`接口而存在的,而且仅被使用了唯一一次,所以使用匿名内部类的语法即可省去该类的单独定义,即匿名内部类:

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(三)匿名内部类的好处与弊端

好处:匿名内部类可以帮我们**省去实现类的定义**;

弊端:匿名内部类的语法——**确实太复杂了!**

(四)语义分析

1、匿名内部类

仔细分析该代码中的语义,Runnable接口只有一个run方法的定义:

abstract void run();即制定了一种做事情的方案(其实就是一个函数):

- 无参数:不需要任何条件即可执行该方案。

- 无返回值:该方案不产生任何结果。

- 代码块(方法体):该方案的具体执行步骤。

2、Lambda

同样的语义体现在`Lambda`语法中,要更加简单:

() -> System.out.println("多线程任务执行!")

- 前面的一对小括号即run方法的参数(无),代表不需要任何条件;

- 中间的一个箭头代表将前面的参数传递给后面的代码;

- 后面的输出语句即业务逻辑代码。

 

六、Lambda标准格式

(参数类型 参数名称) -> { 代码语句 }

小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。

`->`是新引入的语法格式,代表指向动作。

大括号内的语法与传统方法体要求基本一致。

 

七、练习:使用Lambda标准格式(无参无返回)

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

八、Lambda的参数和返回值

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(一)传统写法

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(二)代码分析

- 为了排序,Arrays.sort方法需要排序规则,即Comparator接口的实例,抽象方法compare是关键;

- 为了指定compare的方法体,不得不需要Comparator接口的实现类;

- 为了省去定义一个ComparatorImpl实现类的麻烦,不得不使用匿名内部类;

- 必须覆盖重写抽象compare方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;

- 实际上,只有参数和方法体才是关键。

(三)Lambda写法

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

九、练习:使用Lambda标准格式(有参有返回)

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

十、Lambda省略格式

(一)可推导即可省略

Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以根据上下文推导得知的信息,都可以省略。

例如上一题还可以使用Lambda的省略写法:

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

(二)省略规则

1、小括号内参数的类型可以省略;

2、如果小括号内**有且仅有一个参**,则小括号可以省略;

3、如果大括号内**有且仅有一个语句**,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。

备注:掌握这些省略规则后,请对应地回顾本章开头的多线程案例。

十一、练习:使用Lambda省略格式

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

第十二章、异常、多线程、Lambda表达式_黑马Java第57期个人学习笔记_个人笔记

十二、Lambda的使用前提

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

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

2、使用Lambda必须具有**上下文推断**。

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

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