并发编程学习笔记 一 线程中断 两阶段终止模式 线程状态 synchronized原理

并发编程学习笔记 一

  • 线程中断
  • 线程的状态
  • synchronized原理
    • 重量级锁
    • 轻量级锁
    • 偏向锁

线程中断

两种 interrupt() 方法执行情况:

  1. 当被中断的线程正处于 sleep join wait 状态
    在执行 thread.interrupt() 后,都会终止线程的阻塞状态,但是不会将该线程的中断标志为置为 true。
  2. 当被中断的线程正处于正常运行的状态时
    在执行 thread.interrupt() 后,并不会立即终止该线程的执行,而是会先将该线程的中断标志为置为 true,当该线程内部检测到中断标志为改变时,可以选择先去料理后事(如释放锁资源,连接资源等),再自行了断,这样可以优雅的终止线程,大大降低强制终止线程产生问题的几率。

设计模式之:两阶段终止模式的简单实现

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. 操作系统中线程的五种状态:
    (1)初始状态(2)可运行状态(就绪)(3)运行态(4)阻塞态(5)终止态

  2. Java中定义的线程的六种状态
    (1)NEW
    (2)RUNNABLE
    (3)BLOCKED
    (4)WAITING
    (5)TIMED_WAITING
    (6)TERMINATED

synchronized原理

  1. java的对象头
    在 32 位虚拟机中,java的对象头共占有 8 个字节,其中有 4 个字节的 MarkWord 和 4 个字节的 KlassWord。
  2. MarkWord格式
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 (标记为垃圾回收)

重量级锁

  1. 在 sychronized 给对象上锁(重量级锁)之后,MarkWord 会指向操作系统中的对象 Moniter。
  2. Moniter
    Moniter 翻译为监视器管程,大体上有三部分组成:
    (1)Owner :一个管程只能有一个 Owner ,代表当前正占有锁的线程,一开始为 null 。
    (2)EntryList:表示一个存放被阻塞线程的集合,也就是常说的阻塞队列。
    (3)WaitSet:存放之前获取过锁,但由于某些原因进入 WAITING 状态的线程。
  3. 自旋优化:在重量级锁竞争的过程中,当前线程在检测到锁已被占用后不会立即进入阻塞队列,而是占用 cpu 再进行若干次尝试,失败后才会进入阻塞队列,以减少线程上下文切换成本。
    注意:因为线程需要占用 cpu 不断尝试获取锁,所以自旋优化仅仅使用与多 cpu。

轻量级锁

  1. 应用场景:如果一个对象在多线程环境下需要加锁,但是加锁的时间时错开的,也就是没有竞争,那么可以使用轻量级锁来优化(避免使用 Moniter 导致开销大),轻量级锁对用户是透明的,语法仍然是 synchronized。

  2. 工作流程:
    (1)当调用 sychronized 给对象加锁时,每个线程的栈帧都会包含一个锁记录(lock record)对象,锁记录内部可以存储锁对象的 MarkWord。
    (2)尝试给对象加锁,让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录的地址,以及标记 Mark Word 最后两位为 00 ,标志该对象已被加上轻量级锁。

  3. cas 交换失败的两种情况
    (1)当前线程想要执行 cas 交换时,发现对象头的 Mark Word 最后两位为 00 ,并且不是自己加的锁,说明已经有其他线程加上了锁,自己执行锁升级流程。
    (2)当前线程想要执行 cas 交换时,发现对象头的 Mark Word 最后两位为 00 ,但是发现是自己加的锁,自己执行锁重入流程,添加一条 Lock Record 作为重入的计数,此时锁记录中的 Object reference 仍然指向锁对象,但存储 Mark Word 的位置为 null。

  4. 当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头
    成功,则解锁成功
    失败,说明轻量级锁进行了锁升级或已经升级为重量级锁,进入重量级锁解锁流程

  5. 锁升级
    如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有
    竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。即为锁对象申请 Moniter ,让锁对象的 MarkWord 指向 Moniter,然后自己进入 EntryList 阻塞。

偏向锁

  1. 应用场景:当轻量级锁没有竞争时,自己每次重入都要执行 CAS 操作,并创建栈帧。Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。

  2. 偏向锁的撤销
    (1)调用对象的 hashcode:因为偏向锁没有额外存储对象 hashcode 的空间,只能将对象头改为正常状态,也就是取消偏向锁状态。
    (2)当有其他线程使用锁(无竞争):会使偏向锁升级为轻量级锁。
    (3)调用 wait / notify 方法:因为该方法属于重量级锁的范围,自然会撤销偏向锁。

  3. 批量重偏向
    如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象
    的 Thread ID,当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至
    加锁线程。

  4. 批量撤销
    当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象
    都会变为不可偏向的,新建的对象也是不可偏向的

你可能感兴趣的:(java,java,并发编程,锁)