THREAD
Intro(Thread) #
状态 #
源码
创建方式 #
1. 重写run方法 #
重写
Runnable
的run方法相当于自定义target,start启动后调用start0()native方法给线程分配运行资源,近而重新调度到Thread
的run方法。并传递给Runnbale target
中的run方法。
因为这个是直接通过Thread类启动的。所以run方法没有被重载。所以会调度到target –> run()方法中。其余的都是扩展,比如new Thread(futureTask),其实 FutureTask 一起实现了 Runnable, Future接口。运行结束后通过futureTask对象的get()阻塞获取值。另外FutureTask构造器需要Callable
2. 继承Thread #
而继承的线程和刚才的实现接口不太一样的地方是:不需要target,也无需调用target的 run()方法。并且整个运行过程中target为NULL.
因为整个Thread对象run方法被子类PrimeThread
给重写了。线程相关的方法 #
(sleep,join,wait,interrupted,interrupt,isInterrupted)
2. join #
1). join() <=等价与=> join(0)
2). 等待该线程死亡的时间最多为millis
毫秒。0
意味着永久等待。
3). 通过循环调用this.wait
来实现join逻辑,需要在当前线程存活的情况下。
4). 当一个线程终止时,this.notifyAll
方法会被调用。所以thread1
无限wait到thread2
对象上时。thread1
被唤醒的情况之一就是thread2
死亡,JVM会调用thread2
线程对象的notifyAll方法,释放wait在上面的其他资源(例如:thread1
)。3. wait/notify/notifyAll #
JVM会为一个使用内部锁(synchronized)的对象维护两个集合,Entry Set和Wait Set,也有人翻译为锁池和等待池,意思基本一致。 参考
wait,notify
容易造成的死锁
解释:假设有四个线程,C1,C2,P1,P2 正在锁池
现在C1,拿到锁,没数据,进入等待池,C2 同理进入等待池
P1 拿到锁,生产,notify C1进入锁重新争取。P1退出。此时P2也在。若P2拿到锁,进入等待池。此时P2,C2都在等待池。
C1 拿到锁,消费,notify P2 –> 正常情况
C1 拿到锁,消费,notify C2 –> C2,P2会一直在等待池中。造成死锁4. interrupted/interrupt/isInterrupted #
1). interrupt()用于设置中断标志。如果在碰见以下情况:清除标志位,并且抛出一个
InterruptedException
异常
此线程正在调用一个对象的wait()
,join()
,或者sleep()
方法。等等…
2). 静态方法,获取当前线程的中断标志,并给native方法传参数true
清除标志位。
换句话说,如果连续调用两次这个方法。则第二次会返回false
。
3). 返回中断标志。
4). 中断探测与是否清空标志的native方法。interrupt()
设置中断标志native isInterrupted(boolean clean)
:false -> isInterrupted()
返回标志,不清除。true -> interrupted()
返回标志,并清除。代码示例
synchronzied关键字 #
锁住代码,对象解释
1. 对象锁和类锁不同步
2. 加synchronized的方法,线程会读锁,而没有加的线程不会读锁,直接进入执行。(也就是一个对象两个syn方法会同步,一个syn方法和普通方法不同步)
3. 加载方法上通过字节码方法修饰符ACC_SYNCHRONIZED
保证,代码块通过字节码指令monitorenter
,monitorexit
来保证。volatile关键字 #
1). 原理及作用:为了提高处理器的执行速度,在处理器和内存之间增加了多级缓存来提升。但是由于引入了多级缓存,就存在缓存数据不一致问题。但是,对于volatile变量,当对volatile变量进行写操作的时候,JVM会向处理器发送一条lock前缀的指令,将这个缓存中的变量回写到系统主存中。
但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议
缓存一致性协议:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
所以,如果一个变量被volatile所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个volatile在并发编程中,其值在多个缓存中是可见的。 参考2). 可见性和有序性实现:查看加入
volatile
关键字编译后的汇编代码,发现多了一个lock
前缀指令。它实际上相当于一个内存屏障
(也称内存栅栏),提供三个功能。1. 它确保指令重排时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面,即在执行到内存屏障这句指令时,在它前面的错作已经全部完成。
2. 它会强制将对缓存的修改立即写入主存。
3. 如果是写操作,它会导致其他cpu中对应的缓存行无效。ThreadLocal #
1). 主要是通过ThreadLocal对象给Thread中的属性
ThreadLocalMap
赋值。
但是因为这个属性ThreadLocalMap
是默认类型,也只允许同包下的类可以访问。所以外部[我们编写的代码]不可以直接访问这个属性。
2). ThreadLocal造成内存泄露:
继承WeakReference
的目的是为了调用super(k)
,将这个Entry中的ThreadLocal类型的key进行虚化。
因为Entry节点在赋值的时候是强引用,Entry中的value也是强引用,而里面的属性key是虚引用。这样就会形成Entry entry = new Entry(null, value)
的效果。因为ThreadLocal对象已经回收。所以这个entry正常不会使用了。也就造成内存泄露了。
当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。生产者消费者模型 #
thread pool #
- 线程池的创建方式
- 线程池的几个核心参数
- 线程池的运行流程
- https://juejin.cn/post/7137186083074703391
- https://zhuanlan.zhihu.com/p/112527671
TODO #
Reference #
- 你真的懂wait、notify和notifyAll吗
- 读懂isInterrupted、interrupted和interrupt
- https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html
- https://stackoverflow.com/questions/5218969/why-is-thread-stop-so-dangerous
- https://stackoverflow.com/questions/15680422/difference-between-wait-and-blocked-thread-states
- https://www.zhihu.com/question/329746124
- https://github.com/12302-bak/idea-test-project/tree/v2.0.0-BAK/_0_base-learning/src/main/java/_base/thread/_volatile
- https://drive.google.com/file/d/1r_DtdgyC9bHP-Y-poLb5fbQIttrWTkAa/view?usp=sharing