美文网首页
源码角度理解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

    Synchronized加到方法上的用法 当传入不同对象的时候,并不能起到加锁的目的。synchronized作用...

  • 从源码角度深入理解Glide(下)

    上两篇文章从源码角度深入理解Glide(上)和从源码角度深入理解Glide(中)中,我们已经把Glide加载图片的...

  • 源码角度理解ReentrantLock

    源码分析参见参考1 使用场景参见参考2 小结 ReentrantLock可以实现公平锁和非公平锁,Synchron...

  • Volley

    Android Volley完全解析(四),带你从源码的角度理解Volley

  • 从锁升级的角度理解synchronized

    前言 在 Java 中为保证线程安全,可以使用关键字 synchronized 保护代码,在多个线程之间同时只能有...

  • Volley源码解析

    原博客地址参考资料:Android Volley完全解析(四),带你从源码的角度理解VolleyVolley 源码...

  • Handler从源码角度理解

    上一个文章讲解了Handler的基本使用,同时也有一些问题没有解决,本篇带你从源码的角度理解。首先让我们来看看Ha...

  • Kotlin 的锁和多线程同步

    Synchronized.kt 的源码: JvmFlagAnnotations.kt 的源码: 如何使用 Sync...

  • Handler详解(完结)

    一,Handler源码分析 Android异步消息处理机制完全解析,带你从源码的角度彻底理解(郭霖)[https:...

  • synchronized和lock的区别(底层实现)

    从使用的角度看 synchronized 和 lock 的区别 起初 java 中只有 synchronized ...

网友评论

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

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