实现线程有几种方法
两种方法对比:
1 代码架构角度,代码任务应该和Thread类解耦,不应该混为一滩。
2 使用Runnable,利用后续线程池,大大减少线程损耗,继承Thread类执行任务,有线程创建,销毁等损耗
3 继承Thread 类 ,因为不能继承其他类,大大限制可扩展性
两种方法本质区别:
Thrad run方法是怎么写的 ,
target其实也是一个runnable,在构造函数时候传入,
方法一 是直接调用传入target的run方法。
方法二 是整个都被重写了。
思考题: 同时用两种方法会怎么样?
第二个run 覆盖了Thread里面的三行run方法,即使传入了Runable方法, 但已经被覆盖了,所以 Runable里面的run 不可执行。
错误理解:
线程池 为什么不是 创建线程的另一种方式?
因为线程池本质还是通过 ThreadFactory 基本都是DefaultThreadFactory创建Thread,只不过传入参数更多了。
通过Callable和FutureTask创建线程,也算一种新建线程的方式?
定时器观点?
本质还是 那两类方法
这两种连类的包装都没有,其实只是语法层面的包装。其实停留在表现,都是实现方式。
本质要说,其他随意。
其他方式看源码都没有逃脱其他方式。
启动线程
start方法含义
调用start方法,不代表立刻运行,要有线程调度器去决定什么时候去运行。
start方法启动两次
一个线程两次调用start()方法会出现什么情况?为什么?
会抛出异常,以为在启动start方法时候,回去检查线程状态,如果是不符合标准的状态,将会抛出异常
看源码:
为什么调用start方法,而不是直接执行run方法啊?
调用start方法才是真正意义上启动了一个线程,经历线程的各个声明周期,run就是执行一个普通方法
停止线程
如何正确停止线程
java没有停止线程的机制,只有使用interrupt这种合作机制,interrupt 是中断,不是停止,在Java中最多能做的就是中断,交给线程决定停止。但是线程自己不想中断,咱门无能为力,把停止线程,交给执行线程的本身。
其实这个问题,就是如何使用interrupt 进行通知,其他线程如何配合,这个就是核心
停止线程的最佳实践
1 run方法完毕了
2 线程有异常出现,又没有处理 就停止了
停止了,资源就会被JVM回收。
通常情况下,如何停止线程
直接调用interrupted 不会中断
需要配合才能停止,有效果,在1秒室内推断
在sleep中阻塞,进行中断,会抛出异常InterruptedException。
这时候需要捕获异常,响应中断
是否还需要isInterrupted这个判断,大部分时间是会消耗在sleep(10)之中,所以这个判断是多余的。所以还是会抛出异常,
内try catch, sleep函数是native 方法,一旦响应中断,便会把Sleep标记位清除,再查就查不到了,也就是说,响应中断了,但是由于设计理念,标记位会被清除。看源码OpenJDK。
所以检查不到任何中断迹象。
处理终端最佳实践:
传递中断:
这个处理是最应该选择的,这样做,在run方法中会强制trycatch
这个方法是别人写的,自己只是调用,就是坑。
在方法中把中断给吞了。没有上报,无法做出更多的处理,方法编写人有问题。
应该添加签名,强制在run方法中进行trycatch,run方法无法再次抛出了。
在这里可以感知中断,正确去处理这个中断。按照自己的思路进行相应。
恢复中断思路:
在catch子语句中调用Thrad.currentThread().intrrupt()来恢复设置中断状态,以便在后续执行中,依然能够检查到刚刚发生了中断。
在run方法中 在进行处理,无抛出异常,无法进行trycatch ,但是可以自己判断处理
最不应该做的就是屏蔽异常。
相应中断的方法列表:
有相应中断的能力,这些方法,将线程陷入阻塞,可以用interrupt方法中断线程
为什么要用Interrupt?
被相应的线程,有能力自己处理线程逻辑,更加安全,并完成了清理,数据完整性得到了保障
错误停止线程的方式:
stop方法会释放掉所有的monitor,有些说法是错误的,看官网
suspend 不会释放锁,是带着锁去休息的,容易造成死锁。
volatile 是不行的:
看着好像挺好的,确实停止了。
但是有些时候是有问题的。
100的倍数会放入队列。
消费者:
主函数:
原因就是,阻塞队列java设计者设计的时候是可以相应中断的,你们用野方法,当然不行。
停止线程 重要方法 源码解析
以及其他判断线程是否中断的方法
gitHub 去看openJDK
另一个interrupted方法:
返回之后,清除了线程中断状态
这个方法不清楚 中断转态。
Java 异常体系
runtime Exception一定是程序员的问题。
生命周期
线程的生命周期是什么–参考答案就是这幅图
Thread 和Object 类中线程相关方法
线程优先级、4个属性总结、线程属性面试问题
通常不同设置守护线程。
不应该设置线程优先级,来帮助程序运行。
线程异常处理
异常体系图很重要,要看下
try catch 只能捕获对应线程内的异常
解决方法:
检测出线程里面出现的异常,并进行相关处理
这个接口是Thread类里面的一个接口
三种方法可以做这件事情:
处理线程问题, 进行特殊处理,比如通知管理员,进行管理。
JMM
Java 内存模型
1 起因,多核情况下 表现不一致
2 一组规范,jvm ,cpu,java代码,帮助开发者 容易开发
3 重排序,可见性,原子性(例子,好处,可见性抽象图,happens-before,votaile,sync 以及两者关系,原子性,哪些操作原子性)
商用JVM 上 没有这个问题,32位操作系统不是,64位是,
死锁问题解决方案
因为锁只能被一个线程所获取,所以会死锁
死锁的影响
结束死锁时候看下退出信号,不是0,而是别的信号
实际工作情况中发生的问题: 银行转账问题
假设程序通讯有间隙
这回控制台什么都不打印了,都卡在了sync,正是因为 transferMoney 是 from to 在不同线程里面是相反的
500人随机转账,依然会发生死锁;
因为是随机的,所以你转给我,我转给你的概率很小了。但结果还是会有死锁发生。
死锁发生的四个必要条件:
最好一个线程一把锁
数据库就是靠剥夺条件,来解决死锁的
要构成环路,才不可以解开。
就比如之前的必然死锁例子,o1 不能被多个线程同时拥有
请求o2 的时候 保持o1 满足第二个条件
没有人去剥夺
如何定位死锁:
Jps 命令行查询 ThreadId
很轻易的分析出来了呀,但如果死锁不明显,也可以帮咱们分析下线程栈持有那些锁,也能很好分析的。
这就找到了呀,互相等待的关系,多个线程都是相对等待的关系啊。
用代码方式解决问题
在未来经常会用锁的地方,加上这种保护,无论什么机制,及时发现,及时解决
如何修复死锁问题
不可预料,蔓延快,破坏性大
几个月几年才发生一次,就可以先不考虑了。
要想清楚意见事情
冲突时候要有加时赛。引入一把新锁,总有人会先抢到。
对于转账进行修改
在实际开发过程中,使用主键来获取高低顺序
哲学家就餐问题
解决哲学家问题的方法:
领导调节,有一个领导巡视,如果看到哲学家不行,就命令不许吃,创造剥夺条件。
死锁恢复机制:
有可能哲学家特别老实,每次都放下,就会饿死。
实际开发中 避免死锁
死锁化解了,锁释放了,这么线程1,2 就成功获取了。
降低锁的使用粒度,用不同的锁而不是一个锁,降低临界区。
自己制定锁对象,就有个控制权
命名的重要性
不同功能用不同的锁对象,而不是很多功能都用一个锁对象。
活锁
等待时间变为随机数,在进行放下
活锁消耗CPU 资源
如何解决活锁问题:
不停的重试,导致整个程序无法运行。
Msg 不一定放到头部,重试限制,因为Msg 可能是个毒瘤,耗尽资源。
饥饿
优先级复习,编程不应该依赖优先级
避免方式:
1 编程的时候,不应该使用线程优先级这个概念。
2 编程不应该有锁不释放情况发生。