两种 interrupt() 方法执行情况:
设计模式之:两阶段终止模式的简单实现
public class Test1 { public static void main(String[] args) throws InterruptedException { TwoParseTermination twoParseTermination = new TwoParseTermination(); twoParseTermination.start(); Thread.sleep(3500); twoParseTermination.stop(); } } class TwoParseTermination{ private Thread monitor; public void start(){ monitor = new Thread(()->{ while(true){ if(Thread.currentThread().isInterrupted()){ System.out.println("进行线程结束前的料理后事工作(如释放锁资源)"); break; }else{ try { Thread.sleep(1000); System.out.println("记录监控日志"); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } } }); monitor.start(); } public void stop(){ monitor.interrupt(); } }
操作系统中线程的五种状态:
(1)初始状态(2)可运行状态(就绪)(3)运行态(4)阻塞态(5)终止态
Java中定义的线程的六种状态
(1)NEW
(2)RUNNABLE
(3)BLOCKED
(4)WAITING
(5)TIMED_WAITING
(6)TERMINATED
Mark Word (32 bits) | State |
---|---|
identity_hashcode:25 /age:4 /biased_lock:1 /lock:2 后三位001 | Normal (正常) |
thread:23 (线程唯一标识)/ epoch:2 age:4 biased_lock:1 lock:2 后三位101 | Biased(偏向锁) |
ptr_to_lock_record:30 (指向线程栈中的锁记录) /lock:2 后二位00 | Lightweight Locked (轻量级锁) |
ptr_to_heavyweight_monitor:30 (指向 Moniter 对象) /lock:2 后二位10 | Heavyweight Locked(重量级锁) |
lock:2 后二位11 | Marked for GC (标记为垃圾回收) |
应用场景:如果一个对象在多线程环境下需要加锁,但是加锁的时间时错开的,也就是没有竞争,那么可以使用轻量级锁来优化(避免使用 Moniter 导致开销大),轻量级锁对用户是透明的,语法仍然是 synchronized。
工作流程:
(1)当调用 sychronized 给对象加锁时,每个线程的栈帧都会包含一个锁记录(lock record)对象,锁记录内部可以存储锁对象的 MarkWord。
(2)尝试给对象加锁,让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录的地址,以及标记 Mark Word 最后两位为 00 ,标志该对象已被加上轻量级锁。
cas 交换失败的两种情况
(1)当前线程想要执行 cas 交换时,发现对象头的 Mark Word 最后两位为 00 ,并且不是自己加的锁,说明已经有其他线程加上了锁,自己执行锁升级流程。
(2)当前线程想要执行 cas 交换时,发现对象头的 Mark Word 最后两位为 00 ,但是发现是自己加的锁,自己执行锁重入流程,添加一条 Lock Record 作为重入的计数,此时锁记录中的 Object reference 仍然指向锁对象,但存储 Mark Word 的位置为 null。
当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头
成功,则解锁成功
失败,说明轻量级锁进行了锁升级或已经升级为重量级锁,进入重量级锁解锁流程
锁升级
如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有
竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。即为锁对象申请 Moniter ,让锁对象的 MarkWord 指向 Moniter,然后自己进入 EntryList 阻塞。
应用场景:当轻量级锁没有竞争时,自己每次重入都要执行 CAS 操作,并创建栈帧。Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。
偏向锁的撤销
(1)调用对象的 hashcode:因为偏向锁没有额外存储对象 hashcode 的空间,只能将对象头改为正常状态,也就是取消偏向锁状态。
(2)当有其他线程使用锁(无竞争):会使偏向锁升级为轻量级锁。
(3)调用 wait / notify 方法:因为该方法属于重量级锁的范围,自然会撤销偏向锁。
批量重偏向
如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象
的 Thread ID,当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至
加锁线程。
批量撤销
当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象
都会变为不可偏向的,新建的对象也是不可偏向的