线程(特性/生命周期/创建方式)
2021-02-25 21:49:36 1 举报
AI智能生成
登录查看完整内容
线程(特性/生命周期/创建方式)
作者其他创作
大纲/内容
喜欢收藏+点赞👍 谢谢
进程和线程
进程是操作系统分配资源的基本单位
线程是操作系统执行调度的基本单位
线程切换
CPU
ALU:计算单元
Registers:寄存器组(用来存数据)
PC:程序计数器
由操作系统的线程调度器控制
平分时间片算法
CFS算法
线程的特性
可见性
存在问题
数据的读取顺序:从内存读到 L3 → L2 → L1 → 寄存器后,ALU开始做计算
再次读取数据时,寄存器优先从 L1(本地缓存)中读取,不会重新从内存中读
读取不到最新数据
解决
lock前缀指令
触发总线锁,独占对象的使用,保证对象被修改时刷新到主内存
触发MESI协议,使其他CPU缓存中该变量的副本失效
volatile
happen-before原则
MESI缓存一致性协议
有序性
指令重排(单线程没有影响)
多线程下会有问题,比如DLC单例会取到半成品
内存屏障
禁止指令重排序
原子性
多线程下线程安全问题
比如100个线程做累加,最后结果小于预期值
加锁
生命周期
6 种状态
新建(NEW)
使用 new 关键字创建线程后进入新建状态,此时还没有调用 start()
可运行(RUNNABLE)
线程调用 start() 方法后,进入就绪状态,等待CPU分配时间片操作系统中就绪(READY)和运行中(RUNNING)两种状态的统称
阻塞(BLOCKED)
当进入 synchronized 同步代码块或同步方法时,且没有获取到锁,线程就进入了 blocked,直到锁被释放,重新进入 runnable 状态
等待(WAITING)
当线程调用 wait() 或者 join() 时,会进入到 waiting 状态,当调用notify 或 notifyAll 时,或者 join 的线程执行结束后会进入 runnable
超时等待(TIMED_WAITING)
当线程调用 sleep (time)或者 wait (time)时,进入 timed waiting 状态当休眠时间结束后,或者调用 notify 或 notifyAll 时会重新进入 runnable
终止(TERMINATED)
程序执行结束,线程进入 terminated 状态
扩展
java.lang.Thread类的源码中有个内部枚举,这里可以看到6种状态
创建线程的方式
继承Thread类
重写run方法,没有返回值
new MyThread().start();
缺点:Java是单继承,如果继承Thread就不能继承其他类
代码实现
实现Runnable接口
在创建Thread对象时传进去
优点:不受单继承的限制
实现Callable接口
重写call方法,有返回值,可抛异常
需要用FutureTask在外部封装一下再传递给Thread,FutureTask就是Runnable的实现类
FutureTask使用场景?
两件事或多件事同时完成
FutureTask两个个构造函数?
第一个构造函数要求传入Callable对象
第二个构造函数要求传入Runnable对象和返回值类型
FutureTask的其他方法?
get()
会一直等待子线程运行结束
传入等待时间,超时后会抛出TimeoutException异常,需要捕捉处理
isDone()
询问子线程是否执行完成,返回值是boolean
优点
在主线程中可获取到子线程的返回值
直接调用FutureTask对象的get()方法
为什么可以获取到?如何获取的?
在主线程中可获取到子线程发生的异常
通过getCause()方法获取子线程的异常
使用线程池(单独重点介绍)
终止/退出线程的方式
run() 方法运行结束
如何判断线程是否停止
this.interrupted()
返回线程状态并停止线程
this.isInterrupted()
不具备清除状态功能
使用退出标志位/共享变量
定义全局变量,使用volatile修饰的boolean退出标志位来控制循环
调用interrupt()方法终止线程区分阻塞和和非阻塞两种情况
调用interrupt()时会抛出异常,处于阻塞状态中的线程可捕获interruptedException异常,通过break跳出循环
调用interrupt()时会调用interrupted()函数,未阻塞的线程可使用isInterrupted()判断线程的中断标志来退出循环
守护线程
在java线程中有两种线程,一种是用户线程,一种是守护线程,典型得守护线程就是垃圾回收线程
守护线程是一种特殊得线程,当进程中不存在用户线程(非守护线程)时,守护线程自动销毁
可以使用setDaemon()设置线程为守护线程,注意不能把一个正在运行的线程设置为守护线程所以,setDaemon()方法必须在start()方法前面,并且守护线程中产生得线程也是守护线程
常见问题
谈谈你对线程安全的理解?
当多个线程访问一个对象时,如果不进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的
什么时候考虑线程安全?
多个线程访问同一个资源
资源是有状态的,比如字符串拼接
如何做到线程安全?
使用Synchronized关键字给代码块或者方法加锁
比如StringBuffer的源码中,方法上均添加了Synchronized
sleep()与wait()的区别?
sleep() 属于Thread类,wait() 属于Object类
sleep() 不会释放对象锁,wait() 会释放对象锁
sleep() 必须指定时间,wait() 可指定也可以不指定
sleep() 可以使用在任何代码块,wait() 必须在同步方法或同步代码块中使用
思考:为什么wait要定义在Object中而不定义在Thread中?
Java的锁是对象级别的,不是线程级别的
sleep() 休眠指的就是线程休眠,所以在Thread类
思考:为什么wait必须写在同步代码块中?
避免 CPU 切换到其他程,而其他线程又提前执行了 notify 方法,那这样就达不到我们的预期(先 wait 再由其他程来唤醒),所以需要一个同步锁来保护
notify()和notifyAll()有什么区别?
使用notifyall可以唤醒所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个,推荐使用notifyall
interrupted()和isInterrupted()的区别?
interrupted查询当前线程的中断状态,并且清除原状态
isInterrupted仅仅是查询当前线程的中断状态
Java中用到的线程调度算法是什么?
有两种调度模型:分时调度模型和抢占式调度模型
java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU
0 条评论
回复 删除
下一页