美文网首页
源码角度理解Synchronized

源码角度理解Synchronized

作者: Aaron_Swartz | 来源:发表于2019-10-07 16:18 被阅读0次
    • Synchronized加到方法上的用法
    public class AccountingSync implements Runnable {
        // 共享资源: 这里是静态资源才可以被多个线程操作
        static int i = 0;
    
        public synchronized void increase(){
            ++i;
        }
        public static  synchronized  void increase(){
            ++i;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100000; ++i) {
                increase();
            }
        }
    
        public static void main(String[] args) {
            AccountingSync async = new AccountingSync();
            AccountingSync async1 = new AccountingSync();
            Thread t1 = new Thread(async); 
            Thread t2 = new Thread(async1);
            try{
                t1.start(); // 当调用线程的t1.run()方法的时候其实并没有启动线程的操作,仅仅是函数调用而已。
                t2.start();
                t1.join();
                t2.join();
            } catch (InterruptedException e){
                //
            }
            System.out.println(i);
        }
    }
    

    当传入不同对象的时候,并不能起到加锁的目的。synchronized作用于非静态方法时,锁住的是实例,此时此实例的其它synchronized方法也不能被其他线程访问,但是非synchronized 方法可以被其他线程访问。当synchronized作用于静态方法时锁住的是类。

    • synchronized加到代码块上的用法:这个时候要注意,锁住的对象是静态的,还是非静态,一般类中静态变量所有实例共享,但是非静态变量每个实例单独拥有。
    public class AccountingSync implements Runnable {
    
        static Object instance = new Object();
       // Object instance = new Object(); 这样写会有问题
    
        // 共享资源: 这里是静态资源才可以被多个线程操作
        static int i = 0;
    
        public static synchronized void staticIncrease(){
            ++i;
        }
    
        @Override
        public void run() {
            synchronized (instance) {
                for (int j = 0; j < 100000; ++j) {
                    ++i;
                }
            }
        }
        public static void main(String[] args) {
            Thread t1 = new Thread(new AccountingSync());
            Thread t2 = new Thread(new AccountingSync());
            try{
                t1.start(); // 当调用线程的t1.run()方法的时候其实并没有启动线程的操作,仅仅是函数调用而已。
                t2.start();
                t1.join();
                t2.join();
            } catch (InterruptedException e){
                //
            }
            System.out.println(i);
        }
    }
    
    • synchronized底层语义原理
      • synchronized同步代码块的底层原理


        image.png

    从字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。

    • synchronized同步方法原理

    方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。

    • Java虚拟机对synchronized的优化

    锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。

    • 偏向锁:在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁(会涉及到一些CAS操作,耗时)的代价而引入偏向锁。偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。
    • 轻量级锁
    • 自旋锁: 轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,毕竟操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,因此自旋锁会假设在不久将来,当前的线程可以获得锁。
    • 锁消除:Java虚拟机在JIT编译时,通过扫描去除不可能存在共享资源竞争的锁,从而消除锁,减少开销。
    • 等待唤醒机制与synchronized
      所谓等待唤醒机制本篇主要指的是notify/notifyAll和wait方法,在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常,这是因为调用这几个方法前必须拿到当前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,在前面的分析中,我们知道monitor 存在于对象头的Mark Word 中(存储monitor引用指针),而synchronized关键字可以获取 monitor ,这也就是为什么notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法调用的原因。
    synchronized (obj) {
           obj.wait();
           obj.notify();
           obj.notifyAll();         
     }
    

    需要特别理解的一点是,与sleep方法不同的是wait方法调用完成后,线程将被暂停,但wait方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll方法后方能继续执行,而sleep方法只让线程休眠并不释放锁。同时notify/notifyAll方法调用后,并不会马上释放监视器锁,而是在相应的synchronized(){}/synchronized方法执行结束后才自动释放锁。同时,线程sleep()方法会导致线程进入阻塞态。

    参考:
    1 synchronized的三种应用方式

    相关文章

      网友评论

          本文标题:源码角度理解Synchronized

          本文链接:https://www.haomeiwen.com/subject/wrxmpctx.html