多线程简介
线程概念
进程与线程
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位
线程:cpu的最小执行单位是线程
线程串行:归根结底就是单条线程来执行多个任务
线程并行:同一个时刻发生,并且在时间上是重叠的
进程与线程关系
- 一个线程只能属于一个进程,而一个进程可以有多个线程。(至少有一个线程)
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)
- 处理机分给线程,即真正在处理机上运行的是线程
- 线程在执行过程中,需要协作同步。
不同的线程要利用消息通信的办法实现同步
多线程使用场景
- 批量处理任务
向大量用户发送邮件 丨 处理大批量文件
- 实现异步
快速响应用户 丨 自动作业处理
- 增大吞吐量
tomcat 丨 数据库
总结使用场景特点 :逻辑之间无依赖关系,可同时执行,则可以应用多线程技术进行优化
线程组(ThreadGroup)
ThreadGroup的提出是为了方便线程的管理,通过它可以批量设定一定想成的属性,也可以通过线程组方便的获得线程的一些信息。
尽量不要使用,会带来线程安全问题
启动线程
new Thread().start();
执行逻辑
Runable
Callable<T>
Runable与Callable区别
- 两者都是接口,Callable有call方法,Runable有run方法
- Callable有返回值,Runable没有返回值
- Callable返回值进行泛型化,创建时传递进去,执行结束后返回
- Callable方法抛出异常,run方法无法抛出异常
- Callable可以获取执行动态,中途取消。Runable无法获取执行状态
Runable与Callable联系
- Runable 实例对象需要 Thread 包装类启动
- Callable call 方法实际是在 Runable 的 run 方法中执行
- Callable 先通过 FutureTask 方法包装成 Runable ,在交给 Thread 包装执行
终止线程
stop:可能导致线程安全问题,JDK不建议使用
destroy: JDK中删除方法
标志位:代码逻辑中增加一个标志位
线程守护
- 守护线程
当进程不存在或主线程停止,守护线程也会被停止 - 用户线程
指用户自定义创建的线程,主线程停止,用户线程不会停止
注意:
- thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadException异常,你不能把正在运行的常规线程设置为守护线程
- 在Daemon线程中产生的新线程也是Daemon的
- 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断
线程状态
- NEW
尚未启动的线程的线程状态 - RUNABLE
可运行线程的线程状态,等待CPU调度 - BLOCKED
线程等待状态 // 处于方法中被阻塞 - WAIT
线程等待状态 // 不带Timeout参数的方式调用Object,wait,Thread join,LockSupport.park - TIMED_WAITING
具有指定等待时间的线程的线程状态 // 带超时的方式:
Thread.sleep、Object.wait、Thread.join、LockSupport.parkNanos、LockSupport.parkUntil - TERMINATED
线程执行完毕,已经退出
interrupt
线程不会被中断
- interrupt 不会中断线程,只是打上中断标记
- 不会切断线程的状态
- 只会改变 interrupt 的状态
在Waiting、Timed Waiting状态使用
wait()、wait(long)、join()、join(long,int)、join(long,int)、sleep(long,int)或sleep(long,int)等方法后
处于Waiting、Timed Waiting状态使用,该线程调用interrupt 方法后,线程的Waiting、Timed Waiting状态被消除,并抛出InterruptException异常
park()、parkNanos() 方法执行后
线程也处于Waiting、Timed Waiting,也会被唤醒,但是不会抛出异常,有时会出现挂起失效
目标是I/O或者NIO
如果目标线程是I/O 或者NIO中的Channel 所阻塞,同样I/O操作会被中断或者返回特殊异常值,达到终止线程的目的
终止线程
可以通过while循环 判断!Thread.currentThread().isInterrupted() 来达到终止线程的目的
线程间的通信方式
数据交互
- 文件共享
- 网络共享
- 共享变量
线程协作
JDK中提供的协调API
- suspend / resume
调用suspend挂起目标线程,通过resume可以恢复线程执行
容易出现死锁
- 死锁的suspend/resume。 suspend并不会像wait一样释放锁,故此容易写出死锁代码
- 在Thread.sleep 使用时间不对等的时候 会永久性的挂起
- wait / notify (notifyAll)
作用:
wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁
notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程
注意:
- 虽然会wait自动解锁,但是对于顺序有要求,如果在notify被调用之后,才使用wait的调用,线程会永远处于waiting状态
- 这些方法只能由同意对象锁的持有者线程调用,也就是在同步块里面,否则会抛出IllegalMonitorStateException异常
- park / unpark
线程调用park则等待"许可",unpark 方法为指定线程提供"许可(permit)"
- 调用unpark之后,再调用park,线程会直接运行
- 提前调用unpark不叠加,连续多次嗲用unpark后,第一次调用park后后拿到"许可"直接运行,后续调用会进入等待
伪唤醒
是指线程并非因为notify、notifyall、unpark等api调用而意外唤醒
代码中用if句判断,是否进入等待状态,这样的做法是错误的
官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待,程序可能在没有满足条件结束的情况下退出