对象锁与类锁

什么是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位):
对象锁与类锁_第1张图片
由此可以看见每个对象中记录了锁的信息,其中包括了锁的状态和当前锁的指针。
所以对于一个被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对象的锁,两者之间没有交集,所以不会阻塞等待。

你可能感兴趣的:(对象锁与类锁)