文章目录
第17章 多线程基础
基本概念
- 程序:编写的指令集合,其实就是我们写的代码
- 进程:运行中的程序
- 线程
- 单线程
- 多线程
- 并发
- 并行
线程使用
继承 Thread 类
- 重写 run 方法(来自 Runnable 接口,Thread 类是实现了 Runnable 接口的 run 方法),写上自己的业务逻辑
- 当类继承 Thread 类,该类就可以当做线程
- sleep 休眠方法(必须有异常处理)
- start 启动线程方法,调用 run 方法(为什么不直接调用 run 方法?)
- currentThread().getName() 获取当前线程
多线程机制
- 运行程序后,创建了这个程序的进程
- 进入主方法后,这个进程创建了一个主线程(main线程)
- 当主线程内再新建了一个继承 Thread 类的对象,这时主线程创建了一个子线程
- 子线程运行的同时,主线程不会阻塞,仍然继续执行
- 当主线程运行完后,会终止消失,但若主线程的子线程还没有执行完,子线程不会消失,而是继续执行。当所有线程结束后,进程才会结束
- 可以使用 Jconsole 监控线程执行情况
为什么是 start 方法?
- 主线程直接调用 run 方法,则 run 方法会被当作一个普通的方法,主线程要执行完 run 方法后才会执行主线程剩余的代码,即主线程被阻塞。故直接调用 run 方法,没有真正地创建一个子线程,必须调用 start 方法
- start 源码里会执行 start0 方法是本地方法(native方法),由 jvm 机和操作系统 调用
- 调用 start0 方法后,只是将线程变成了可运行状态,具体什么时候上 CPU 执行,需要根据操作系统的调度算法
- 调用 start 方法才会创建新线程,start 调用 run 方法不会创建新线程
实现 Runnable 接口(建议使用)
- Java 是单继承的,如果一个类已经继承了其他类,这时只能直接实现 Runnable 接口
- 直接实现时,没有 start 方法(不能直接调用 run 方法!)
- 解决方式是创建一个 Thread 对象,将实现 Runnable 接口的对象放进去,再通过 Thread 对象调用 start 方法
Thread thread = new Thread(dog);
- 底层使用静态代理设计模式
- 可以让两个线程来执行一个实现 Runnable 接口的对象,更适合多个线程共享一个资源的情况
线程终止
终止的两种方式
- 线程完成任务时
- 通过使用变量来控制 run 方法退出(修改线程类里的循环判断变量),即通知方式
线程常用方法
用户线程和守护线程
- 用户线程
- 守护线程(举例:垃圾回收机制)——怎么设置守护线程?setDaemon方法
常用方法
-
setName
-
getName
-
interrupt 中断线程,但并没有真正结束线程,只是进入休眠状态(sleep)
如果线程正在休眠,则结束休眠(一般用法)
-
setPriority 设置优先级**(优先级一般用于进程调度算法)**
- MIN_PRIORITY = 1(最低)
- NORM_PRIORITY = 5(普通)
- MAX_PRIORITY = 10(最高)
-
getPriority
-
start
-
run
-
sleep
-
yield 线程礼让(让出CPU让其他进程执行,但不保证成功,因为有时内核资源不紧张,CPU觉得没有必要礼让。且礼让时间不确定)
-
join 线程插队,即抢占(一旦插队成功,则肯定先执行完插入的线程的所有的任务,即使是主线程也暂时不执行)
线程的生命周期
线程状态
- 官方文档有6种
- New
- Runnable(就绪态,可分为两个状态,这就是某些书上说7种状态的由来)
- Ready
- Running
- Blocked(阻塞)
- Waiting
- TimedWating(超时等待)
- Teminated(终止)
- 某些书上说的是7种
- 查看状态用 getState 方法
线程同步
- 线程同步机制概念
Synchronized 关键字 —— 实现同步的具体方法
-
同步代码块(建议选择,范围较小,效率尽可能高)
synchronized (对象) { //得到对象的锁,才能操作同步代码 //需要被同步的代码 }
-
放在方法声明种,表示为同步方法
public synchronized void func(String name){ //需要被同步的代码 }
互斥锁
- 是一个标记
- 用 synchronized 修饰时,拥有互斥锁
- 同步局限性:效率降低
- 非静态同步方法的锁可以加在 this(这个对象),也可以加在其他对象(要求是线程使用的是同一个对象、共享的对象,即这个其他对象来当锁,这个对象不能有很多个。如果有很多个,虽然锁住了,但是每个线程都能抢到锁,实际上相当于没锁住)
- 静态同步方法的锁只能加在类本身(类名.class)
- 同步方法如果没有使用static修饰:默认锁对象为this
- 如果方法使用static修饰,默认锁对象:当前类.class
- 注意多个线程的锁对象必须是同一个!
线程死锁
- 死锁概念
释放锁
释放锁的情况
- 当前线程的同步方法、同步代码块执行结束
- 当前线程的同步方法、同步代码块遇到break、return
- 当前线程的同步方法、同步代码块出现了未处理的Error或Exception,导致异常结束
- 当前线程的同步方法、同步代码块执行了线程对象的wait()方法,当前线程暂停,并释放锁
不会释放锁的情况
-
当前线程的同步方法、同步代码块调用 sleep、yield 方法
-
执行同步代码块时,其它线程调用该线程的挂起方法 suspend(使其进入到就绪态)
注:尽量避免使用 suspend 和 resume 方法,已不再推荐使用