zoukankan      html  css  js  c++  java
  • Synchronize

    Synchronize实现原理

    java对象头中存在一个指向monitor对象的指针。每个java对象在内存中都对应一个monitor对象。monitor对象就是用来存放对象的锁信息的。

    monitor对象重要属性:
    count:用来存放当前对象被获取锁的次数,0表示对象没有被线程占有。
    owner:存储当前占用对象锁的线程
    waitSet:存放阻塞状态线程(里边的线程都是block状态,只有调用了notify方法后才会变成runable状态)
    entryList:就绪状态线程(里边的线程都是runable状态)
    entryList与waitSet的区别:waitSet中的线程只有通过notify方法后才能有资格抢占对象锁,entryList中的线程只要对象释放锁后,cpu就会通知他们取抢占对象锁。

     static class Father extends Thread{
            @Override
            public void run() {
                try{
                    sleep(3000);
                    synchronized (this){
                        notify();//notify后,主线程进入到entryList中 ,然后主线程和子线程共同争抢cpu资源
                    }
                    System.out.println("主线程被唤醒");
                }catch (Exception e){
                }
            }
        }
        public static void main(String[] args) {
            System.out.println("主线程开始运行");
            Father f = new Father();
            synchronized (f){
                try{
                    f.start();
                    System.out.println("主线程被阻塞");//此时主线程进入到waitSet中
                    f.wait();
                }catch (Exception e){
                }
            }
            System.out.println("主线程继续运行");
        }
    

    锁方法和锁代码块的区别
    锁代码块:会在代码块前后边增加monitorEnter和monitorexit指令
    锁方法:会在方法区中记录该方法为加锁方法
    都是用来标识代码块需要做同步操作,只不过标识的方式不一样

    锁膨胀

    无锁状态 -- 偏向锁 -- 轻量级锁 -- 重量级锁
    image
    文章开头介绍的monitor对象指的是图中重量级锁部分,线程获取对象锁并不是直接抢占这个monitor对象的,而是一步步升级的。
    1、一个对象创建完后,对象头的结构为无锁那一行(后续随着锁升级对象头的结构也一步步变化),对象头的锁状态为无锁状态
    2、线程1执行代码遇到monitorEnter指令,开始获取锁。
    3、查看对象头锁状态为无锁状态,直接更新图中A为当前线程1的id,更新锁状态为偏向锁
    4、偏向锁不会自动释放,所以当线程1执行完毕后,图中A依然是线程1的id
    5、线程1又来获取锁的时候,直接比较图中A记录的id是否和当前线程一致,如果一致则直接执行代码块。
    6、如果线程2来获取锁,比对A的id和当前线程不一致,则查看线程1是否还存活着,如果不在存活,则将对象头设置为无锁状态。然后线程2又获得了该对象的偏向锁。
    如果线程1还存活着,暂停线程1,锁升级为轻量级锁。

    轻量级锁过程:
    到现在对象头为轻量级锁,线程1和线程2共同争抢。
    7、线程1和线程2分别在自己的线程栈中开辟一块空间,复制对象头中MarkWord 。然后通过cas操作将对象头中B指向自己线程栈中复制的MarkWord。假设线程1成功抢占锁资源。线程2则通过空自旋继续尝试(假设尝试上线为100次),在自旋到100次内线程1把锁释放,则线程2获取锁成功。

    重量级锁:
    8、如果线程2在自旋达到100次或者期间又来个线程3,线程1还没有释放锁,则将锁升级为重量级锁。
    9、升级为重量级锁,则会将图中C指向对象的monitor,然后线程2和线程3进入到entryList中。直到线程1释放锁,线程2和线程3竞争锁。
    重量级锁竞争过程通过count属性判断是否可以获取锁,如果count!=0,则线程进入entryList。
    线程执行过程中如果调用wait方法,则释放锁,线程进入waitSet,直到调用notiry方法,线程进入entryList。

    jvm对Synchronize底层优化

    另外jdk还对锁有一些优化:
    锁粗化:比如在主线程有个循环,每个循环都会获取同一个对象锁,然后在释放。jdk发现后会自动把加锁操作放在循环外层提升效率。
    锁自旋:1、锁从轻量级升级到重量级的过程中,线程要进入到entryList,线程cpu状态的转换是消耗性能的 ,
    2、并且,A尝试获取锁的时候,B正在持有锁执行代码,但是B很可能在很短的时间内释放锁
    上面两个原因,所以A可以不进入阻塞状态,自旋等待B释放锁。
    锁消除:jdk会自动消除一些不必要的锁。

  • 相关阅读:
    (转)6 个重构方法可帮你提升 80% 的代码质量
    (转)卸载和安装LINUX上的JDK
    (转)一些国外优秀的elasticsearch使用案例
    (转)Redis 集群方案
    (转)理想化的 Redis 集群
    (转)Hprose与WCF在云计算平台Azure上的对决
    (转).NET技术+25台服务器怎样支撑世界第54大网站
    Hbase split的过程以及解发条件
    test
    HTTP和HTTPS详解
  • 原文地址:https://www.cnblogs.com/yanhui007/p/12585924.html
Copyright © 2011-2022 走看看