对象锁与类锁

什么是sycnchronized synchronized是Java中的关键字,是一种同步锁。在JDK1.6以前,使用synchronized就只有一种方式即重量级锁,而在JDK1.6以后,引入了偏向锁,轻量级锁,重量级锁,来减少竞争带来的上下文切换。它修饰的对象有以下几种:

  1. 代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
    public class Test { private final Object lock = new Object(); { synchronized(lock){ // doSomething } } }

  2. 方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
    public class Test { public synchronized void test(){ // doSomething } }

  3. 静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
    public class Test { public static synchronized void test(){ // doSomething } }

  4. 类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
    public class Test { { synchronized(Test.class){ // doSomething } } }

对象锁与类锁 几个现象
【对象锁与类锁】首先我来举几个例子
  1. 一个类中有两个被synchronized修饰的普通方法,生成一个对象延时调用结果会如何
    public class Test1 { public static void main(String[] args) { Things things = new Things(); new Thread(() -> things.doThing1(), "A").start(); SleepUtils.sleep(1); new Thread(() -> things.doThing2(), "B").start(); } static class Things { public synchronized void doThing1(){ SleepUtils.sleep(5); System.out.println("I'm doing thing one"); }public synchronized void doThing2(){ SleepUtils.sleep(1); System.out.println("I'm doing thing two"); } } }// 输出结果 // I'm doing thing one // I'm doing thing two

  2. 一个类中有两个被synchronized修饰的普通方法,生成两个个对象延时调用结果会如何
    public class Test2 { public static void main(String[] args) { Things things1 = new Things(); Things things2 = new Things(); new Thread(() -> things1.doThing1(), "A").start(); SleepUtils.sleep(1); new Thread(() -> things2.doThing2(), "B").start(); } static class Things { public synchronized void doThing1(){ SleepUtils.sleep(5); System.out.println("I'm doing thing one"); }public synchronized void doThing2(){ SleepUtils.sleep(1); System.out.println("I'm doing thing two"); } } }// 输出结果 // I'm doing thing two // I'm doing thing one

  3. 一个类中有两个被synchronized修饰的静态方法,生成两个对象延时调用结果会如何
    public class Test3 { public static void main(String[] args) { Things things1 = new Things(); Things things2 = new Things(); new Thread(() -> things1.doThing1(), "A").start(); SleepUtils.sleep(1); new Thread(() -> things2.doThing2(), "B").start(); } static class Things { public static synchronized void doThing1(){ SleepUtils.sleep(5); System.out.println("I'm doing thing one"); }public static synchronized void doThing2(){ SleepUtils.sleep(1); System.out.println("I'm doing thing two"); } } }// 输出结果 // I'm doing thing one // I'm doing thing two

  4. 一个对象有被synchronized修饰的一个静态方法和一个普通方法,生成两个对象延时调用结果会如何
    public class Test4 { public static void main(String[] args) { Things things1 = new Things(); Things things2 = new Things(); new Thread(() -> things1.doThing1(), "A").start(); SleepUtils.sleep(1); new Thread(() -> things2.doThing2(), "B").start(); } static class Things { public static synchronized void doThing1(){ SleepUtils.sleep(5); System.out.println("I'm doing thing one"); }public synchronized void doThing2(){ SleepUtils.sleep(1); System.out.println("I'm doing thing two"); } } }// 输出结果 // I'm doing thing two // I'm doing thing one

为什么会出现这种现象
对象组成 对象由对象头、实例数据和补齐数据组成。
对象头由MarkWord、指向类的指针和数组长度组成。
其中MarkWord记录了对象和锁有关的关系,在此只介绍MarkWord。
MarkWord内部划分如下图所示(32位):
对象锁与类锁
文章图片

由此可以看见每个对象中记录了锁的信息,其中包括了锁的状态和当前锁的指针。
所以对于一个被synchronized修饰的方法被访问时该线程会将该对象的无锁转态转化为偏向锁(轻量级锁或重量级锁),后面的线程想要访问该方法时,会不断判断该对象是否有锁,当锁不存在了,就抢占这个对象的所有权。
解析上述现象
  • 对于例一,有一个对象things,首先线程A抢占了对象A的锁(所有权),然后休眠了五秒(睡眠不释放锁,即一直保持things的所有权),主线程休眠了一秒后,线程B执行,发现对象things已经被占领了,于是它就不断的尝试获取对象things锁(所有权),知道线程A释放了锁,线程B才执行dothing2。
  • 对于例二,由于有两个对象,所以线程A即使抢占了对象things1的锁(所有权),并不会干扰线程B抢占对象things2的锁(所有权),所以线程B在休眠了两秒之后(主线程一秒,线程B一秒)就直接打印出来了。
  • 对于例三,在对象实例化时,会先在JVM中寻找该类是否被加载到虚拟机中,不存在的话会先进行类加载...等一系列操作,然后会生成一个类对象,保存了该类的信息,同时生成对象时是通过该类对象进行生成的,比如对于Thing这个类,他会有一个生成Thing这个类的类对象,所以访问被sycnchronized修饰的静态方法的线程会将抢占该类对象的锁(所有权),所以线程A抢占了该类的类对象的锁,线程B只能等待线程A释放,然后继续运行。
  • 对于例四,就很明了了,线程A抢占的是类对象的锁,线程B抢占的是things2对象的锁,两者之间没有交集,所以不会阻塞等待。

    推荐阅读