S锁的原理实现

Synchronized的三种用法

synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础。

synchronized 常见的三种用法如下:

  • 普通同步方法,锁是当前实例对象
  • 静态同步方法,锁是当前类的class对象
  • 同步方法块,锁是括号里面的对象

通过如下代码来分析下synchronized 获取的是哪个对象的锁

public class SynTest {
    private static List<String> list = new ArrayList<String>();
    //当前实例的锁
    public synchronized void add1(String s){
        list.add(s);
    }
    //SynTest.class 锁
    public static synchronized void add2(String s){
        list.add(s);
    }
    //SynTest.class 锁
    public void add3(String s){
        synchronized(SynTest.class){
            list.add(s);
        }
    }
    //当前实例的锁
    public void add4(String s){
        synchronized(this){
            list.add(s);
        }
    }
}

普通同步方法,锁是当前实例对象
add1 方法是synchronized的第一种用法,因为它是普通同步方法,所以获取当前实例的锁。
add2 方法是synchronized的第二种用法,因为它是静态同步方法,所以获取SynTest.class的锁。
add3 方法是synchronized的第三种用法。指定锁:SynTest.class。
add4 方法是synchronized的第三种用法。指定锁:this(当前实例)。

结论:

add1和add4方法的锁都是 当前实例,所以add1和add4 可以实现方法互斥
add2和add3方法的锁都是SynTest.class,所以add2和add3可以实现方法互斥。
**注意:**add1和add2两个synchronized是不互斥的,因为他们不是同一把锁。只有同一把锁才会互斥。就是 类锁和对象锁也就是方法锁都是锁颗粒度问题

对象锁也叫方法锁,是针对一个对象实例的,它只在该对象的某个内存位置声明一个标识该对象是否拥有锁,所有它只会锁住当前的对象,而并不会对其他对象实例的锁产生任何影响,不同对象访问同一个被synchronized修饰的方法的时候不会阻塞,

类锁是锁住整个类,当有多个线程来声明这个类的对象时候将会被阻塞,直到拥有这个类锁的对象呗销毁或者主动释放了类锁,这个时候在被阻塞的线程被挑选出一个占有该类锁,声明该类的对象。其他线程继续被阻塞住

Synchronized 原理

我们通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的:

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

javap
在这里插入图片描述
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。=
  • 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。
  • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

执行monitorexit的线程必须是objectref所对应的monitor的所有者。

指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

public class SynchronizedMethod {
    public synchronized void method() {
        System.out.println("Hello World!");
    }
}

javap
在这里插入图片描述
方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

运行结果解释
有了对Synchronized原理的认识,再来看上面的程序就可以迎刃而解了。

代码段2结果:
虽然method1和method2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。

代码段3结果:
虽然test和test2属于不同对象,但是test和test2属于同一个类的不同实例,由于method1和method2都属于静态同步方法,所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),所以也只能顺序的执行。

代码段4结果:
对于代码块的同步实质上需要获取Synchronized关键字后面括号中对象的monitor,由于这段代码中括号的内容都是this,而method1和method2又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值