这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战
Thread
线程
线程初始化方法
| 类型 | 代码 | 附加 |
|---|---|---|
| Thread | extendsThread Override run use instance.start() | Thread是具体的类 |
| Runable | implements Runnable Override run use new Thread(instance).start() | |
| callable | implements Runnable Override call 丢到线程池里面去 | Callable不同的地方在于,可以带一个返回值 |
三者关系
Thread:继承Runnable接口
Runnable:需要注意的是,在1.8中Runnable已经被标注为FunctionalInterface了。
具体初始化关系
Thread:重写run
Runable:重写run
callable:重写call
线程的状态
-
线程优先级:
1<= x = 5 <=10
- 1为最低,10为最大,5为默认
-
线程状态
在Thread中,定义了(since 1.5)对应的枚举,一共包括:
-
new
-
RUNNABLE
-
BLOCKED
-
WATIING
-
TIME-WAITING
-
TERMINATED
6个状态,相关图示如下:
-
可以看出来,BLOCKED和WAITING本质上都是等待某些临界区资源的状态,只不过:
- BLOCKED是通过Synchronized进行JVM层面上的显式资源上锁
- WAITING是线程本身将资源让出时的底层(被JVM屏蔽的底层操作系统层面的)资源上锁,可能是CPU的执行核心等。
相关方法
Object
-
wait
-
源码描述
当前线程拥有调用wait方法的Monitor时方可调用。
线程将主动释放拥有的monitor,直到:
1.其他线程调用notify/notifyAll时,
2.其他线程interrupt时
3.输入的等待时间耗尽时,
线程才会在重新获取monitor的拥有权之后,重新开始执行。
如果当前线程在wait前或wait后被其他线程interrupt了,会在对象的锁状态被存储后,抛出interruptedException。
源码备注中给出的伪代码案例:
synchronized (obj) { while (<condition does not hold>) obj.wait(); ... // Perform action appropriate to condition } -
事实上,实践中大部分的代码都使用了非循环来进行wait,原因也比较简单:while里面的condition是需要额外成本的,不如直接wait。
-
wait会虚假苏醒(spurious wakeup,即:不在上面的三种情况中),这种情况是应用程序需要自己保证不会发生的。
-
-
notify
-
源码描述
-
方法相关
唤醒一个等待此对象monitor的线程,如果有多个,随机唤醒一个(The choice is arbitrary and occurs at the discretion of the implementation)。
唤醒的线程在本线程放弃lock之后才会继续执行,并且唤醒的线程将正常去竞争对象的同步权,并不会有优势或劣势。
-
如何获取monitor
- 执行对象的synchronized的实例方法。
- 执行对象中的synchronized代码块。
- 执行synchronized的静态方法,此时锁的是class。
-
-
补充:直接把synchronized(对象)也是一种方法,只是没有上述的那么优雅
-
-
notifyAll
- 注释里只说了会全部唤醒,并没有说明顺序
Thread
-
join
- 看代码实际上就是wait了对应的时间。
-
sleep
-
源码描述
等待指定时间,且没有失去当前对monitor的拥有权。
-
-
yield
-
源码描述
暗示(hint)调度器当前线程愿意让出当前处理器的使用权,调度器可以忽视这个暗示。
Yield是一种启发式的尝试,旨在提高线程之间的相对进度,否则会过度使用CPU。需要使用详细的分析和测试(benchmark)来确保确实能达到这样的效果。
不推荐使用这个方法。在以下场合是可能会起作用的:
- 在debug和测试的时候使用,可以帮助一些由于竟态条件(race conditions)引起的bug。
- 在设计一些同步控制构造的时候,例如JUC中的locks。
-
LockSupport
(todo)进一步的源码解析:juejin.cn/post/684490…
-
park
-
parkNanos
-
parkUntil
-
这三者的核心代码都是如下这四句。blocked是代表当前线程parking的同步对象。
Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(true, deadline/0L/nanos); setBlocker(t, null); -
-
源码描述
使当前线程的线程调度无效,直到permit是存在的。
如果permit存在,那么会消费且马上返回;不存在的话,当前线程会对线程调度失效,且停止活动,直到以下的事情发生:
- 其他线程调用unpark方法指定当前线程为对象
- 其他线程interrupt当前线程
- 调用错误返回(that is, for no reason)
这个方法并不会返回导致这个方法return的原因。调用者需要重新确认第一次导致线程park的原因。调用者可以通过线程的interrupt的状态来确定。
wait和park的区别
通过代码的描述,可以看出wait和park是近似的:、
- wait通过notify进行唤醒
- park通过unpark进行唤醒
并且都是主动让出当前的资源调度。
区别如下:(see:juejin.cn/post/684490…)
(1)Object.wait()方法需要在synchronized块中执行;
(2)LockSupport.park()可以在任意地方执行;
(3)Object.wait()方法声明抛出了中断异常,调用者需要捕获或者再抛出;
(4)LockSupport.park()不需要捕获中断异常;
(5)Object.wait()不带超时的,需要另一个线程执行notify()来唤醒,但不一定继续执行后续内容;
(6)LockSupport.park()不带超时的,需要另一个线程执行unpark()来唤醒,一定会继续执行后续内容;
(7)如果在wait()之前执行了notify()会怎样?抛出IllegalMonitorStateException异常;(这一点是不对的,notify的提前调用并不会导致该异常)
(8)如果在park()之前执行了unpark()会怎样?线程不会被阻塞,直接跳过park(),继续执行后续内容;
see : juejin.cn/post/684490…
wait方法通过对象的互斥锁实现线程同步,park是通过一个二元信号量实现线程同步的
wait方法会让线程放弃当前持有的锁,park方法则不会
在wait方法调用前notify是无效的,但可以通过提前调用unpark方法,来让接下来调用park方法的线程提前获取许可(permit值)
\