![]()
实现多线程的方法:
2种。
方法1:实现Runnable接口。
方法2:继承Thread类。
方法1更好,实现runnable接口会更好。
从解耦的角度会更好, 资源也更节约。
方法1、最终调用target.run();
方法2、run()整个都被重写、
错误观点:
通过线程池创建线程的方法并不算一种新建线程的方式。因为他的底层原理还是new thread。
(其中会有一个thread factory)
通过Callable 和Future Task来创建。
![]()
本质也是使用runnable接口。所以也不算一种新的创建线程的方式。
多线程的实现方式,在代码种写法千变万化,但其本质万变不离其宗。
本质上是一种:新建thread类,可以用两种方法来实现:
使用runnable接口和继承thread类并重写其run方法。
实现runnable接口会更好,从代码架构角度去思考应该解耦,
使用thread类,每次都需要新建线程,会造成新建线程的损耗,而使用runnable接口可以反复利用同一个线程。(线程池就是这么做的)
Java不支持双继承,所以不建议继承自thread类。
启动线程的正确和错误方式:
start()方法含义:
启动新线程。 调用start方法的顺序不代表执行顺序。
记住不能重复执行start()方法。
run()方法:
经典三行代码:![]()
要调用start方法来间接调用run方法。
面试题:
一个线程两次调用start方法会出现什么情况?为什么?
会抛出一个异常,因为第一次调用start方法的时候就会对线程进行检查,第二次运行时就会报错。
既然start方法会调用run方法,为什么我们选择调用start方法而不是直接调用run方法。
调用start方法会经历线程的各个生命周期,才是真正创建一个新的线程。
如何停止线程:
原理介绍:使用interrupt来通知,而不是强制停止线程。
while 内 try/catch的问题:
在while内使用tyr catch会导致interrupt的标记位被清除。
在日常工作种,最好是在catch住了异常后在方法签名中抛出异常。
错误的停止方式:
被停用的stop、suspend、和resume方法。。。
第二种、用volatile设置Boolean标记位
stop方法本质上是不安全的,所以已经被jdk停用。
使用volatile设置Boolean,有时候可以停止,但是当生产者消费者模型中,生产者生产的速度远大于消费者消费的速度时,就会出现阻塞的问题。。导致无法中止。。
如何使用open jdk去查看本地方法:
isInterrupted方法和isInterrupted的不同点在于:
前者只关注当前线程,后者是个静态方法,更关注主线程的中断与否、
![]()
volatile的Boolean方法无法处理长时间阻塞的情况。
如何处理不可中断的阻塞:
没有一种万能的方法,针对不同的情况,使用一些特定的方法,尽可能让它做到能够响应中断。
线程的生命周期:
线程一共有哪六种状态:
![]()
一旦线程使用了start方法,则会进入runnable状态,
![]()
计时等待比等待多一点:参数内时间到了会自动醒,也可以被唤醒。
![]()
等待和notified区域是不同的,前者无法获得锁,而后者是可以获得锁的、
如果从waiting被唤醒后,没有获得锁,会进入blocked。
此外,waiting出现异常后也可以直接跳转到terminated状态。
![]()
常见面试问题:
0-100奇偶数打印。
手写生产者消费者设计模式:
为什么线程通信的方法wait,notify和notifyAll被定义在objec里面,而sleep定义在Thread类里。
wait和sleep最关键的区别在于:
sleep是不释放synchronized的minitor锁的。也不释放lock(独占锁)。
sleep方法响应中断:
1、抛出InterruptedException
2、清除中断状态
![]()
我们不应该调用Thread.wait,因为Thread不适合作为锁对象,Thread在执行完代码后会自动唤醒。
join方法:
新的线程加入了我们,所以我们要等待他执行完再出发。
main等待子线程执行完毕、
join源码用的是wait,但是没有用notify。但是退出的时候JVM会自动有一个notify。
yield方法解释:
作用就是释放我的CPU时间片。
但是JVM不保证遵循。
yield和sleep的区别:
是否随时可能被再次调度。
线程ID:
查看GetId源码可知,第一个主线程的id号是1.原因是:++threadseqnumber,在输出前会先自增,然后再输出。所以先变成1.
而JVM随后会给我们创建很多线程,所以子线程ID肯定不是2.
不要手动设置守护线程。JVM已经给我们自动设置了一些守护线程。
传统的 Try catch是无法捕获子线程的异常。
解决办法:
1、手动在每个run方法里进行Try Catch
2、使用UncaughtExceptionHandler接口。
线程安全问题:
什么是线程安全:不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,都不需要额外做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行,就可以称为线程安全。
线程不安全情况:
1、数据争用
2、竞争条件
比如get的同时set了。再比如额外同步了。
把一个类设计成线程安全的需要更多的设计成本和运行速度,所以在设计类的时候就应该想明白这个类会不会在多线程环境中去执行。
cyclicBarrier 方法。
死锁问题。
逸出问题。如何解决:使用副本和工厂模式。