学习笔记 - 异常和多线程
异常#
概述#
异常指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致JVM的非正常停止。
Java 异常体系#
- java.lang.Throwable:异常体系的父类
- java.lang.Error:Java 虚拟机无法解决的严重问题。如:JVM 系统内部错误、资源耗尽等严重情况。
- java.lang.Exception:其它因编程错误或偶然的外在因素导致的一般性问题,需要使用针对性的代码进行处理,使程序继续运行。
- 编译时异常(受检异常)
- 运行时异常(非受检异常)
异常处理#
Throwable 中的常用方法#
public void printStackTrace()
:打印异常的详细信息,包含异常类型、原因、出现位置,在开发和调试阶段都得使用 printStackTrace。public String getMessage()
:获取发生异常的原因。
try-catch-finally#
- try:程序在执行的过程中,一旦出现异常,就会在出现异常的代码处,生成对应异常类的对象,并将此对象抛出。一旦抛出就不执行其后的代码了。
- catch:针对抛出的异常对象,进行捕获处理。一旦将异常进行了处理,代码就可以继续执行。(可选结构)
- finally:finally 结构中的代码是一定会执行的。(可选结构)
try{
.... //可能产生异常的代码
}
catch(异常类型1 e){
.... //当产生异常类型1时的处置措施
}
catch(异常类型2 e){
.... //当产生异常类型2时的处置措施
}
finally{
.... //无论是否发生异常,都无条件执行的语句
}
何时使用 finally :
- 在开发中,例如输入流、输出流、数据库连接等资源,在使用完之后必须显式的进行关闭操作,避免内存泄漏。
throws + 异常类型#
public void test() throws 异常类型1, 异常类型2, ...{
// 可能存在编译时异常
}
手动抛出异常对象#
通过 throw 关键字手动抛出一个异常对象,如果抛出的异常为 Exception 时,必须处理此异常。
throw 之后的语句不可被执行。
自定义异常类#
继承任一异常类,可以仿照异常类重载构造器。
多线程#
概念#
程序、进程和线程#
- 程序(program):为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
- 进程(process):程序的一次执行过程,或是正在内存中运行的应用程序。
- 每个进程都有一个独立的内存空间,系统运行一个程序即是一个进程从创建、运行到消亡的过程。(生命周期)
- 程序是静态的,进程是动态的。
- 进程作为操作系统调度和分配资源的最小单位(亦是系统运行程序的基本单位),系统在运行时会为每个进程分配不同的内存区域。
- 线程(thread):进程可进一步细化为线程,是程序内部的一条执行路径。一个进程中至少有一个线程。
- 一个进程同一时间若并行执行多个线程,就是支持多线程的。
- 线程作为 CPU 调度和执行的最小单位。
- 一个进程中的多个线程共享相同的内存单元,它们从同一个堆中分配对象,可以访问相同的变量和对象。方便高效的同时可能会带来安全隐患
并行和并发#
- 并行:两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个 CPU 上同时执行。
- 并发:两个或多个事件在同一个时间段内发生。即在一段时间内,有多条指令在单个 CPU 上快速轮换、交替执行,使得在宏观上具有多个进程同时执行的效果
线程的创建方式#
继承 Thread 类#
步骤:
- 创建一个继承于 Thread 类的子类;
- 重写 Thread 类的 run( ) ---> 将此线程要执行的操作,声明在此方法体中
- 创建当前 Thread 的子类对象;
- 通过对象调用 start( ) :启动线程的同时调用当前线程的 run( )。
注意:
- 如果直接通过对象调用 run( ) ,则没有启动多线程;
- 已经 start( ) 的线程不能再次执行 start( ) ,否则会报异常。
实现 Runnable 接口#
步骤:
- 创建一个实现 Runnable 接口的类;
- 实现接口中的 run( ) ---> 将此线程要执行的操作,声明在此方法体中
- 创建当前实现类的对象;
- 将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的实例;
- 通过对象调用 start( ) :启动线程的同时调用当前线程的 run( )。
匿名实现#
new Thread(new Runnable(){
public void run(){
// 方法体
}
}).start();
实现方式对比#
- 共同点:
- 启动线程,使用的都是 Thread 类中定义的 start( )
- 创建的线程对象,都是 Thread 类或其子类的实例
- 不同点:
- 一个是类的继承,一个是接口的实现
- 建议使用实现 Runnable 接口的方式,有以下优点:
- 实现接口的方式避免了类的单继承性的局限性
- 更适合处理有共享数据的问题
- 实现了代码和数据的分离
- 联系:
- Thread 类也实现了 Runnable 的接口(代理模式)
线程的常用结构#
构造器#
public Thread()
:分配一个新的线程对象。public Thread(String name)
:分配一个指定名字的新的线程对象。public Thread(Runnable target)
:指定创建线程的目标对象,它实现了 Runnable 接口中的 run( )。public Thread(Runnable target, String name)
:分配一个带有指定目标新的线程对象并指定名字。
常用方法#
start()
:启动线程,调用线程的run()
;run()
:将线程要执行的操作,声明在run()
中;currentThread()
:获取当前执行代码对应的线程;getName()
、setName()
:获取和设置线程名;sleep()
:静态方法,可以使当前线程睡眠指定时间;yield()
:释放 CPU 的执行权;join()
:在线程 a 中通过线程调用该方法,会使线程 a 进入阻塞状态,知道线程 b 执行结束后线程 a 才继续执行;isAlive()
:判断当前线程是否存活。
过时的方法:
stop()
:强行结束线程(可能会遗留类似数据库连接的资源),不建议使用;void suspend() / void resume()
:可能造成死锁,不建议使用。
线程的优先级#
getPriority() / setPriority()
:获取和设置线程的优先级
Thread 类内部声明的三个常量:
- MAX_PRIORITY(10):最高优先级
- MIN_PRIORITY(1):最低优先级
- NORM_PRIORITY(5):普通优先级,默认优先级( main 线程具有普通优先级)
线程的生命周期#
jdk5之前的#
jdk5之后的#
线程安全问题#
当使用多线程访问同一个资源时,若多个线程只有读操作,那么不会发生线程安全问题。但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。
同步代码块#
synchronized(同步监视器){
// 同步监视器俗称锁,拿到锁的线程才可以执行被同步的代码,同步监视器为唯一的对象
// 需要被同步的代码(即操作共享数据的代码)
}
注意:
- 在实现 Runnable 接口的方式中,同步监视器可以考虑使用
this
; - 在继承 Thread 类的方式中,同步监视器要慎用 this ,可以考虑使用
当前类.class
同步方法#
如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法
权限修饰符 synchronized 返回类型 方法名(){
// 方法体
}
注意:
- 非静态的同步方法,默认同步监视器是
this
; - 静态的同步方法,默认同步监视器是
当前类.class
; - 可用来解决单例模式中懒汉式的线程安全问题
优缺点#
- 好处:解决了线程安全问题;
- 弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低。
死锁#
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
触发死锁的原因#
- 互斥条件
- 占用且等待
- 不可抢夺(或不可抢占)
- 循环等待
Lock 锁#
- 创建 Lock 的实例,需要确保多个线程共用一个 Lock 实例,可以考虑将此对象声明为 static final ;
- 执行
lock()
方法,锁定对共享资源的调用; - 调用
unlock()
方法,释放对共享数据的锁定。
线程的通信#
当我们需要多个线程共同完成一件任务,并且我们希望它们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。
三个方法#
wait()
:线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用;notify()
:一旦执行此方法,就会唤醒被wait()
的线程中优先级最高的那个线程。(如果被 wait() 的多个线程的优先级相同,随机唤醒一个)。被唤醒的线程从当初被 wait 的地方继续执行。notifyAll()
:一旦执行此方法,就会唤醒所有被 wait 的线程。
注意:
- 三个方法必须要在同步代码块或同步方法中使用;
- 三个方法的调用者,必须是同步监视器;
- 这三个方法声明在 Object 类中。
对比 wait 和 sleep#
- 相同点:
- 一旦执行,当前线程都会进入阻塞状态
- 不同点:
- 声明位置:
wait()
:声明在 Object 类中;sleep()
:声明在 Thread 类中,静态的;
- 使用场景不同:
wait()
:只能使用在同步代码块或同步方法中;sleep()
:可以在任何需要使用的场景;
- 使用在同步代码块或同步方法中:
wait()
:一旦执行,会释放同步监视器;sleep()
:一旦执行,不会释放同步监视器;
- 结束阻塞的方式:
wait()
:到达指定时间自动结束阻塞 或 通过被 notify 唤醒结束阻塞;sleep()
:到达指定时间自动结束阻塞;
- 声明位置:
创建线程的其它方式#
实现 Callable#
与之前的方式相比:
-
call()
可以有返回值,更加灵活; -
call()
可以使用 throws 的方式处理异常,更灵活; -
Callable 使用了泛型参数,可以指明具体的
call()
的返回值,更灵活。
如果在主线程中获取分线程 call()
的返回值,则此时的主线程是阻塞状态的。
使用线程池#
提前创建好多个线程,放入线程池中使用时直接获取,使用完放回池中。可以避免频繁创建、销毁,实现重复利用。
优点:
- 提高了程序执行的效率(因为线程已经提前创建好了);
- 提高了资源的复用率(因为执行完的线程并未销毁,而是可以继续执行其它的任务)
- 可以设置相关的参数,对线程池中线程的使用进行管理。
// 1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// 设置线程池的属性
service1.setMaximumPoolSize(50);
// 2.执行指定的线程的操作。需要提供实现 Runnable 或 Callable 接口实现类的对象
service.execute(new NumberThread()); // 适用于Runnable
service.execute(new NumberThread1());
//service.submit(Callable callable); // 适用于 Callable
// 3.关闭连接池
service.shutdown();
作者:Koi.C
出处:https://www.cnblogs.com/KoiC/p/17670336.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
声明:转载或使用请注明本文地址,感谢您的阅读!如果文章内容帮到了您,烦请点一下推荐,不胜感激!
欢迎关注个人公众号!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性