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会自动消除一些不必要的锁。

原文地址:https://www.cnblogs.com/yanhui007/p/12585924.html