异常抛出机制:出现异常,创建异常类型的对象,抛出以捕获;
异常的根父类:throwable-两个子类:exception(编程错误等)和error(jvm无法解决严重问题,eg:堆,栈内存溢出)
exception-分为编译时异常和运行时异常
常见运行时异常:数组越界,空指针,类型不一致,算数,对象类型转换不兼容,数字格式(字符串转数字,字符串中内容不是数字),输入类型不匹配
编译时异常:找不到文件,找不到类,io(读写);
> 将可能出现异常的代码声明在try语句中。一旦代码出现异常,就会自动生成一个对应异常类的对象。并将此对象抛出。
> 针对于try中抛出的异常类的对象,使用之后的catch语句进行匹配。一旦匹配上,就进入catch语句块进行处理。
一旦处理接触,代码就可继续向下执行。
> 如果声明了多个catch结构,不同的异常类型在不存在子父类关系的情况下,谁声明在上面,谁声明在下面都可以。
如果多个异常类型满足子父类的关系,则必须将子类声明在父类结构的上面。否则,报错。
> catch中异常处理的方式:
① 自己编写输出的语句。
② printStackTrace():打印异常的详细信息。 (推荐)
③ getMessage():获取发生异常的原因。
> try中声明的变量,出了try结构之后,就不可以进行调用了。
> try-catch结构是可以嵌套使用的。(catch和finally是可选的,必选一个搭配try)
finally使用:一定要被执行的代码放在其中,无论try-catch中有未被处理的异常还是有return,都会执行finally中语句(例外,system.exit(0)强行终止程序)
哪些语句需放在finally中:需要显式关闭的资源(比如流),防止发生内存泄漏
例如:
@Test
public void test4() {
FileInputStream fis = null;
try{
File file = new File("D:\\hello.txt");
fis = new FileInputStream(file); //可能报FileNotFoundException
int data = fis.read(); //可能报IOException
while(data != -1){
System.out.print((char)data);
data = fis.read(); //可能报IOException
}
}catch(FileNotFoundException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}finally{
//重点:将流资源的关闭操作声明在finally中
try {
if(fis != null)
fis.close(); //可能报IOException
}catch(IOException e){
e.printStackTrace();
}
}
}
}
在方法的声明处增加throws+异常类型
仅将异常抛给方法的调用者,不算真正意义上解决异常
对方法重写的要求(针对编译时异常):抛出异常是父类中的异常类型或是其子类
选择处理方式:- 如果程序代码中,涉及到资源的调用(流、数据库连接、网络连接等),则必须考虑使用try-catch-finally来处理,保证不出现内存泄漏。
- 如果父类被重写的方法没有throws异常类型,则子类重写的方法中如果出现异常,只能考虑使用try-catch-finally进行处理,不能throws。
- 开发中,方法a中依次调用了方法b,c,d等方法,方法b,c,d之间是递进关系。此时,如果方法b,c,d中有异常,我们通常选择使用throws,而方法a中通常选择使用try-catch-finally。
throw与throws区别:前使用在方法内,用于产生异常对象;后使用在方法声明,用于处理异常(向上抛出)
自定义异常:继承于现有的异常体系:runtimeexception/exception;提供几个重载的构造器;提供一个全局常量;
1.继承thread类的子类,重写thread类的run方法(声明该线程要执行的语句),创建对象调用start方法(启动线程,调用当前线程的run方法,不能直接调用run方法,且不能让已经start的线程再次start)
2.创建类实现runable的接口,实现接口的run方法,创建当前类的对象,其作为参数传给thread类的构造器中,创建thread的实例调用start方法
共同点:启动线程都是thread的start方法,创建线程都是thread或其子类的实例
不同点:一个是类的继承,一个是接口的实现。
建议:建议使用实现Runnable接口的方式。
Runnable方式的好处:① 实现的方式,避免的类的单继承的局限性 ② 更适合处理有共享数据的问题(声明的属性共享,唯一;在继承的方法中可以采取向构造器中传入相同对象达到共享数据的目的,或用static声明) ③ 实现了代码和数据的分离。
联系:public class Thread implements Runnable (代理模式)
1. 线程中的构造器
- public Thread() :分配一个新的线程对象。
- public Thread(String name) :分配一个指定名字的新的线程对象。
- public Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法
- public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
2.线程中的常用方法:
> start():①启动线程 ②调用线程的run()
> run():将线程要执行的操作,声明在run()中。
> currentThread():获取当前执行代码对应的线程
> getName(): 获取线程名
> setName(): 设置线程名
> sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数(与wait区别-会释放对同步监视器的调用)
> yield():静态方法,一旦执行此方法,就释放CPU的执行权
> join(): 在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行。
> isAlive():判断当前线程是否存活
过时方法:
> stop():强行结束一个线程的执行,直接进入死亡状态。不建议使用
> void suspend() / void resume() :可能造成死锁,所以也不建议使用
3. 线程的优先级:
getPriority():获取线程的优先级
setPriority():设置线程的优先级。范围[1,10]
Thread类内部声明的三个常量:
- MAX_PRIORITY(10):最高优先级
- MIN _PRIORITY (1):最低优先级
- NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。
synchronized(同步监视器){
//需要被同步的代码
}
说明:
> 需要被同步的代码,即为操作共享数据的代码。
> 共享数据:即多个线程都需要操作的数据。比如:ticket
> 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其它线程必须等待。
> 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。
> 同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。
注意:在实现Runnable接口的方式中,同步监视器可以考虑使用:this。
在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用:当前类.class。
说明:
> 如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。
> 非静态的同步方法,默认同步监视器是this;静态的同步方法,默认同步监视器是当前类本身。
5. synchronized好处:解决了线程的安全问题。 弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低,降低了并发。
单例模式懒汉式的线程安全问题:返回新建实例的方法声明为synchronized(使用volatile关键字,避免指令重排)
步骤1. 创建Lock的实例,需要确保多个线程共用同一个Lock实例!需要考虑将此对象声明为static final
步骤2. 执行lock()方法,锁定对共享资源的调用
步骤3. unlock()的调用,释放对共享数据的锁定
两种方法对比:synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放对同步监视器的调用。
Lock是通过两个方法控制需要被同步的代码,更灵活一些。Lock作为接口,提供了多种实现类,适合更多更复杂的场景,效率更高。
多个线程来共同完成一件任务,并且希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。
wait():线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用
notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait()的多个线程的优先级相同,则随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
注意点:
> 此三个方法的使用,必须是在同步代码块或同步方法中。(Lock需要配合Condition实现线程间的通信)
> 此三个方法的调用者,必须是同步监视器。否则,会报IllegalMonitorStateException异常
> 此三个方法声明在Object类中。
相同点:一旦执行,当前线程都会进入阻塞状态
不同点:
> 声明的位置:wait():声明在Object类中
sleep():声明在Thread类中,静态的
> 使用的场景不同:wait():只能使用在同步代码块或同步方法中
sleep():可以在任何需要使用的场景
> 使用在同步代码块或同步方法中:wait():一旦
执行,会释放同步监视器
sleep():一旦执行,不会释放同步监视器
> 结束阻塞的方式:wait(): 到达指定时间自动结束阻塞 或 通过被notify唤醒,结束阻塞
sleep(): 到达指定时间自动结束阻塞
与之前的方式的对比:与Runnable方式的对比的好处
> call()可以有返回值,更灵活
> call()可以使用throws的方式处理异常,更灵活
> Callable使用了泛型参数,可以指明具体的call()的返回值类型,更灵活
有缺点:如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的。
//创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
//Future类实现了runable接口
FutureTask futureTask = new FutureTask(numThread);
//将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
Thread t1 = new Thread(futureTask);
t1.start();
此方式的好处:
> 提高了程序执行的效率。(因为线程已经提前创建好了)
> 提高了资源的复用率。(因为执行完的线程并未销毁,而是可以继续执行其他的任务)
> 可以设置相关的参数,对线程池中的线程的使用进行管理
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// //设置线程池的属性
// System.out.println(service.getClass());//ThreadPoolExecutor
service1.setMaximumPoolSize(50); //设置线程池中线程数的上限
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
阻塞与同步关系:同步一定阻塞,阻塞不一定同步