精华内容
下载资源
问答
  • synchronized底层原理

    2020-11-01 13:53:46
    synchronized底层原理 我们都知道synchronized可以让代码同步,所谓同步代码就是在同一时刻只能有一个线程执行这段代码。 synchronized可以锁对象,例如 pubilc class Demo{ private Object object; public Demo ...

    synchronized底层原理
    我们都知道synchronized可以让代码同步,所谓同步代码就是在同一时刻只能有一个线程执行这段代码。
    synchronized可以锁对象,例如

    pubilc class Demo{
        private Object object;
        public Demo (Object obj){
        this.obj = obj;
        }
        pubilc void method(){
             synchronized(obj){
                 System.out.println("这里是同步")
          }
        }
    }

    在这里插入图片描述
    可以锁方法,例如
    在这里插入图片描述

    可以锁class对象,像这样同步的静态方法就是锁了当前类的class同步对象
    在这里插入图片描述
    那么synchronized的底层到底是如何实现的呢,我们都听说过synchronized就是锁对象头,那么到底锁的是什么呢,我们先来看看Java对象的布局,Java中的每个对象都有固定的对象头,对象头中有一个mark word,64位的虚拟机中mark word有64个bit,像这样
    在这里插入图片描述
    在这里插入图片描述
    我们现在就研究synchronized的原理,无需关注太多mark word的内容,只要知道在对象头中有两个bit是用了标志锁,有一个bit标志是否偏向锁,所以synchronized给对象加锁就是修改对象这两个锁标志位的数值,至于具体数值是什么其实我们没有必要记忆,所以所谓synchronized锁对象头,就是修改对象头中的锁标志位的数值,所有的线程都是根据这个锁标志位的数值判断,这个对象是否上锁,这个就是synchronized的底层原理
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • Synchronized底层原理

    2020-09-25 11:30:00
    一、Synchronized加锁方式: 1)同步实例方法,锁是当前实例对象 public synchronized void method() { /*修饰实例方法,锁的是当前实例对象*/ } synchronized(this) { /*修饰同步代码块,锁的是当前实例对象*/ }...

    纯属记录个人理解,有误请指出

    一、Synchronized加锁方式:

    1)同步实例方法,锁是当前实例对象

    public synchronized void method() {
        /*修饰实例方法,锁的是当前实例对象*/
    }
    
    synchronized(this) {
        /*修饰同步代码块,锁的是当前实例对象*/
    }
    

    2)同步类方法,锁是该类类对象

    public static synchronized void method() {
        /*修饰静态方法,锁的是该类类对象*/
    }
    
    synchronized(Demo.class) {
        /*修饰同步代码块,锁的是该类类对象*/
    }
    

    3)同步代码块,锁是括号里面的对象

    String object = "";
    synchronized(object) {
        /*修饰同步代码块,锁的是实例对象Object*/
    }
    

    二、Synchronized特性:

    1)互斥性:Synchronized修饰的同步代码块、实例方法、静态方法,多线程并发访问时,只能有一个线程获取到锁,为非公平锁,每个刚来的线程都会尝试下插队,如果插不了才进入等待队列,进入阻塞状态等待。

    2)可见性:某线程 A 对于进入同步块之前或在 synchronized 中对于共享变量的操作,对于后续的持有同一个监视器锁的其他线程可见

    同步方法和同步代码块底层都是通过monitor来实现同步的。每个对象都与一个monitor相关联。

    同步方法是通过方法中的access_flags中设置ACC_SYNCHRONIZED标志来实现;同步代码块是通过monitorenter和monitorexit来实现。两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。

    三、查看ACC_SYNCHRONIZED标志

    查看字节码在终端操作的命令为

    javac SyncDemo.java

    javap -c -verbose SyncDemo

    public class SyncDemo {
        public static int sum = 0;
        public static synchronized void addSum() {
            sum++;
            System.out.println(sum);
        }
    
        public static void main(String[] args) {
            new Thread("a") {
                public void run() {
                    addSum();
                }
            }.start();
            new Thread("b") {
                public void run() {
                    addSum();
                }
            }.start();
        }
    }
    

    查看结果:我们可以看到在flags中有 ACC_SYNCHRONIZED 标志

     public static synchronized void addSum();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED 
        Code:
          stack=2, locals=0, args_size=0
             0: getstatic     #11                 // Field sum:I
             3: iconst_1
             4: iadd
             5: putstatic     #11                 // Field sum:I
             8: return
          LineNumberTable:
            line 20: 0
            line 21: 8
    
    

    四、查看同步代码块的monitor

    public class SyncDemo {
        public static int sum = 0;
        public static void addSum() {
            synchronized (SyncDemo.class) {
                sum++;
                System.out.println(sum);
            }
        }
    
        static SyncDemo syncDemo;
        public static void main(String[] args) {
            new Thread("a") {
                public void run() {
                    addSum();
                }
            }.start();
            new Thread("b") {
                public void run() {
                    addSum();
                }
            }.start();
        }
    }
    

    查看结果:我们可以看到第9行monitorenter加锁,第23行monitorexit释放锁,第29行monitorexit为发生异常释放锁

     public static void addSum();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=2, args_size=0
             0: ldc           #2                  // class pang/one/SyncDemo
             2: dup
             3: astore_0
             4: monitorenter
             5: getstatic     #3                  // Field sum:I
             8: iconst_1
             9: iadd
            10: putstatic     #3                  // Field sum:I
            13: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
            16: getstatic     #3                  // Field sum:I
            19: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
            22: aload_0
            23: monitorexit
            24: goto          32
            27: astore_1
            28: aload_0
            29: monitorexit
            30: aload_1
            31: athrow
            32: return
          Exception table:
             from    to  target type
                 5    24    27   any
                27    30    27   any
          LineNumberTable:
            line 7: 0
            line 8: 5
            line 9: 13
            line 10: 22
            line 11: 32
          StackMapTable: number_of_entries = 2
            frame_type = 255 /* full_frame */
              offset_delta = 27
              locals = [ class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
            frame_type = 250 /* chop */
              offset_delta = 4
    
    

    五、查看monitor中的锁级别

    锁级别有:

    锁状态 1bit(是否偏向锁) 2bit(锁标志位)
    无锁态 0 01
    轻量级锁 0 00
    重量级锁 0 10
    GC标记 0 11
    偏向锁 1 01

    没加锁

    import org.openjdk.jol.info.ClassLayout;
    
    public class SyncDemo {
        static SyncDemo syncDemo;
        
        public static void main(String[] args) {
            syncDemo = new SyncDemo();
            //synchronized (syncDemo) {
                //System.out.println("locking");
                syncDemo.hashCode();
                System.out.println(ClassLayout.parseInstance(syncDemo).toPrintable());
            //}
        }
    }
    

    输出结果:无锁态

    在这里插入图片描述


    加锁之后

    import org.openjdk.jol.info.ClassLayout;
    
    public class SyncDemo {
        static SyncDemo syncDemo;
        
        public static void main(String[] args) {
            syncDemo = new SyncDemo();
            synchronized (syncDemo) {
                System.out.println("locking");
                syncDemo.hashCode();
                System.out.println(ClassLayout.parseInstance(syncDemo).toPrintable());
            }
        }
    }
    

    输出结果:重量级锁

    在这里插入图片描述

    六、Synchronized锁升级过程

    ​ 1、在无锁状态,线程1访问同步块进行CAS修改MarkWord,进入偏向锁状态,因为偏向锁是在单线程的情况

    ​ 2、当进行另一个线程2访问同步块时,CAS修改MarkWord失败,开始撤销偏向锁,升级为轻量级锁

    ​ 3、而在线程2继续访问同步块进行CAS修改MarkWord时,自旋获取锁失败一定次数后,锁膨胀,升级为重量级锁,从而线程阻塞;

    ​ 4、直到等待线程1释放锁唤醒阻塞的线程,开始新的一轮锁竞争。

    展开全文
  • synchronized 底层原理

    2020-10-19 00:50:03
    我们都知道synchroized可以让线程同步...synchronized: 1.锁对象 public class Main { private Main main; public Main(Main main) { this.main = main; } public void method(){ synchronized(main){ Sys

    我们都知道synchroized可以让线程同步,所谓同步,就是同一时刻,只能有一个线程执行这段代码。

    synchronized:

    1.锁对象

    public class Main {
        
        private Main main;
    
        public  Main(Main main) {
            this.main = main;
        }
        
        public void method(){
            synchronized(main){
                System.out.println("这是同步代码块---锁当前对象");
            }
        }
    }
    

    2.锁方法

       public synchronized void method(){
                System.out.println("同步方法--锁this对象");
        }
    

    3.锁class对象

      public synchronized static void method(){
                System.out.println("同步静态方法--锁当前类的class对象");
        }
    

    那么synchronized到底是锁什么呢,我们都知道synchronized是锁对象头,那么到底是锁什么呢。
    我们先来看下java中对象的格式布局,java中都有固定格式的对象头,对象头中有个Mark Word ,64位虚拟中,Mark Word有64个bit。

    25bit 31bit 1bit 4bit 1bit (偏向锁标识) 2bit(锁标志位)
    unused hashCode unused 分带年龄 偏向锁标致 锁标识位

    我们无需关注太多,只需要只知道在对象头中 有2个bit用来标志锁,有1个bit标志是否偏向锁

    1bit (偏向锁标识 2bit(锁标志位
    1:偏向锁 01:无锁
    0:非偏向锁 00:轻量级锁
    10:重量级锁
    11:GC标记

    所以synchronized给对象加锁,就是修改对象锁标志位数值,所有的线程都是根据这个锁标志位的数值进行判断,这个对象是否上锁

    那么synchronized锁是如何升级的

    在jdk1.6之之前,synchronized只有无锁和重量级锁两个级别,在jdk1.6之后,优化了性能,synchronized锁状态分为无锁、偏向锁、轻量级锁、重量级锁几个状态

    • 无锁: 指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
    • 偏向锁: 指当一段同步代码一直被同一个线程所访问时,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗,即提高性能。
    • 轻量级锁: 指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能
    • 重量级锁: 指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。

    用程序测试锁的升级

      <dependency>
                <groupId>org.openjdk.jol</groupId>
                <artifactId>jol-core</artifactId>
                <version>0.9</version>
      </dependency>
    
    默认情况下新建对象是无锁状态
    
    ![public class Main {
        //根据对象输出对象头的64个bit
        public static void main(String\[\] args) {
            Object o=new Object();
            //输出对象头
            String str= ClassLayout.parseInstance(o).toPrintable();
            System.out.println(str);
        }
    }](https://img-blog.csdnimg.cn/20201019001615801.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NzgxNTg4Mg==,size_16,color_FFFFFF,t_70#pic_center)
    
    

    输出:

    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           09 00 00 00 (00001001 00000000 00000000 00000000) (9)
          4     4        (object header)                           70 0d 06 16 (01110000 00001101 00000110 00010110) (369495408)
    Instance size: 8 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    
    

    在这里插入图片描述

    偏向锁

    默认开启,但是要延迟4秒,可以在创建对象前等待5秒

       public static void main(String[] args) throws InterruptedException {
            Thread.sleep(5000);
            Object obj = new Object();
            //开启一个线程,在线程中枷锁
            Thread t1 = new Thread(() -> {
            //使用synchronized 锁obj
                synchronized (obj) { //锁obj对象  偏向锁
                    System.out.println("偏向锁状态");
                    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
                }
            });
            启动i线程
            t1.start();
        }
    

    结果:

    偏向锁状态
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           0d 48 ad 15 (00001101 01001000 10101101 00010101) (363677709)
          4     4        (object header)                           70 0d 06 16 (01110000 00001101 00000110 00010110) (369495408)
    Instance size: 8 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    
    轻量级锁

    接下来在t1 结束后,使用synchronized锁obj对象,由于是t1线程和主线程交替执行,所以进入轻量级锁

        public static void main(String[] args) throws InterruptedException {
            Thread.sleep(5000);
            Object obj=new Object();
            //开启一个线程,在线程中枷锁
            Thread t1=new Thread(()->{
                //使用synchronized 锁obj
                synchronized (obj){//锁obj对象  偏向锁
                    System.out.println("偏向锁状态");
                    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
                }
            });
            //启动线程
            t1.start();
            t1.join();
            synchronized (obj){
                System.out.println("轻量级锁状态");
                System.out.println(ClassLayout.parseInstance(obj).toPrintable());
            }
        }
    

    输出:

    偏向锁状态
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           0d 10 ed 16 (00001101 00010000 11101101 00010110) (384634893)
          4     4        (object header)                           70 0d 06 16 (01110000 00001101 00000110 00010110) (369495408)
    Instance size: 8 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    
    轻量级锁状态
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           20 f5 27 01 (00100000 11110101 00100111 00000001) (19395872)
          4     4        (object header)                           70 0d 06 16 (01110000 00001101 00000110 00010110) (369495408)
    Instance size: 8 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    
    重量级锁

    接下来在主线程锁住obj对象之后,等待两秒 模拟重操作,开启线程t2,让t2线程尝试获取obj锁,就进入重量级锁状态

     public static void main(String[] args) throws InterruptedException {
            Thread.sleep(5000);
            Object obj = new Object();
            //开启一个线程,在线程中枷锁
            Thread t1 = new Thread(() -> {
                //使用synchronized 锁obj
                synchronized (obj) {//锁obj对象  偏向锁
                    System.out.println("偏向锁状态");
                    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
                }
            });
            //启动线程
            t1.start();
            t1.join();
    
            Thread t2 = new Thread(() -> {
                try {
                    //等主线程拿到锁
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj) {
                    System.out.println("重量级锁状态");
                    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
                }
            });
            t2.start();//启动线程t2 尝试获取obj的锁
            synchronized (obj) {
                System.out.println("轻量级锁状态");
                System.out.println(ClassLayout.parseInstance(obj).toPrintable());
                //主线程不要释放锁
                Thread.sleep(2000);
            }
        }
    

    输出:

    偏向锁状态
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           0d 20 c8 15 (00001101 00100000 11001000 00010101) (365436941)
          4     4        (object header)                           70 0d 06 16 (01110000 00001101 00000110 00010110) (369495408)
    Instance size: 8 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    
    轻量级锁状态
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           1c fb b4 00 (00011100 11111011 10110100 00000000) (11860764)
          4     4        (object header)                           70 0d 06 16 (01110000 00001101 00000110 00010110) (369495408)
    Instance size: 8 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    
    重量级锁状态
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           0e e1 98 02 (00001110 11100001 10011000 00000010) (43573518)
          4     4        (object header)                           70 0d 06 16 (01110000 00001101 00000110 00010110) (369495408)
    Instance size: 8 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    
    
    
    无锁状态 偏向锁状态 轻量级锁状态 重量级锁状态
    0(偏向锁标志位)01(锁标志位) 101 000 010

    以上就是synchronized锁升级测试。

    展开全文
  • Synchronized 底层原理

    2019-09-23 16:06:00
    一、Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。 Synchronized的作用主要有三个: 确保线程互斥的访问同步代码 保证共享变量的修改能够及时可见 有效解决...

    一、Synchronized的基本使用

    Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。

    Synchronized的作用主要有三个:

    确保线程互斥的访问同步代码

    保证共享变量的修改能够及时可见

    有效解决重排序问题。

    从语法上讲,Synchronized总共有三种用法:

    (1)修饰普通方法

    (2)修饰静态方法

    (3)修饰代码块

    接下来我就通过几个例子程序来说明一下这三种使用方式(为了便于比较,三段代码除了Synchronized的使用方式不同以外,其他基本保持一致)

    1.1、没有同步的情况

    代码段一:

    package com.paddx.test.concurrent;
    
    public class SynchronizedTest {
      public void method1(){
        
        System.out.println("Method 1 start");
        try {
          System.out.println("Method 1 execute");
          Thread.sleep(3000);
        } catch (InterruptedException e) {
           e.printStackTrace();
        }
        System.out.println("Method 1 end");
      }
    
      public void method2(){
        System.out.println("Method 2 start");
        try {
          System.out.println("Method 2 execute");
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("Method 2 end");
      }
    
      public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();
    
        new Thread(new Runnable() {
          @Override
          public void run() {
            test.method1();
          }
        }).start();
    
        new Thread(new Runnable() {
          @Override
          public void run() {
            test.method2();
          }
        }).start();
      }
    }
    

    执行结果如下,线程1和线程2同时进入执行状态,线程2执行速度比线程1快,所以线程2先执行完成,这个过程中线程1和线程2是同时执行的。
    Method 1 start
    Method 1 execute
    Method 2 start
    Method 2 execute
    Method 2 end
    Method 1 end

    1.2、对普通方法同步

    代码段2:

    package com.paddx.test.concurrent;
    
    public class SynchronizedTest {
      public synchronized void method1(){
        System.out.println("Method 1 start");
        try {
          System.out.println("Method 1 execute");
          Thread.sleep(3000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("Method 1 end");
      }
    
      public synchronized void method2(){
        System.out.println("Method 2 start");
        try {
          System.out.println("Method 2 execute");
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("Method 2 end");
      }
    
      public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();
    
        new Thread(new Runnable() {
          @Override
          public void run() {
            test.method1();
          }
        }).start();
    
        new Thread(new Runnable() {
          @Override
          public void run() {
            test.method2();
          }
        }).start();
      }
    }
    

    执行结果如下,跟代码段一比较,可以很明显的看出,线程2需要等待线程1的method1执行完成才能开始执行method2方法。

    Method 1 start
    Method 1 execute
    Method 1 end
    Method 2 start
    Method 2 execute
    Method 2 end

    1.3、静态方法(类)同步

    代码段三:

    package com.paddx.test.concurrent;
    
    public class SynchronizedTest {
      public static synchronized void method1(){
        System.out.println("Method 1 start");
        try {
          System.out.println("Method 1 execute");
          Thread.sleep(3000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("Method 1 end");
      }
    
      public static synchronized void method2(){
        System.out.println("Method 2 start");
        try {
          System.out.println("Method 2 execute");
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("Method 2 end");
      }
    
      public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();
        final SynchronizedTest test2 = new SynchronizedTest();
    
        new Thread(new Runnable() {
          @Override
          public void run() {
            test.method1();
          }
        }).start();
    
        new Thread(new Runnable() {
          @Override
          public void run() {
            test2.method2();
          }
        }).start();
      }
    }
    

    执行结果如下,对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法)

    所以即使test和test2属于不同的对象,但是它们都属于SynchronizedTest类的实例,所以也只能顺序的执行method1和method2,不能并发执行。
    Method 1 start
    Method 1 execute
    Method 1 end
    Method 2 start
    Method 2 execute
    Method 2 end

    1.4、代码块同步

    代码段四:

    package com.paddx.test.concurrent;
    
    public class SynchronizedTest {
      public void method1(){
        System.out.println("Method 1 start");
        try {
          synchronized (this) {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
          }
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("Method 1 end");
      }
    
      public void method2(){
        System.out.println("Method 2 start");
        try {
          synchronized (this) {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
          }
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("Method 2 end");
      }
    
      public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();
    
        new Thread(new Runnable() {
          @Override
          public void run() {
            test.method1();
          }
        }).start();
    
        new Thread(new Runnable() {
          @Override
          public void run() {
            test.method2();
          }
        }).start();
      }
    }
    

    执行结果如下,虽然线程1和线程2都进入了对应的方法开始执行,但是线程2在进入同步块之前,需要等待线程1中同步块执行完成。
    Method 1 start
    Method 1 execute
    Method 2 start
    Method 1 end
    Method 2 execute
    Method 2 end

    2、Synchronized 原理

    如果对上面的执行结果还有疑问,也先不用急,我们先来了解Synchronized的原理

    再回头上面的问题就一目了然了。我们先通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的:

    package com.paddx.test.concurrent;
    public class SynchronizedMethod {
      public synchronized void method() {
        System.out.println("Hello World!");
      }
    }
    

    反编译结果:

    在这里插入图片描述
    关于这两条指令的作用,我们直接参考JVM规范中描述:

    monitorenter :
    这段话的大概意思为:

    每个对象有一个监视器锁(monitor),当monitor被占用时就会处于锁定状态。

    线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

    如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者

    如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1

    如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权

    monitorexit:
    这段话的大概意思为:

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

    指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。

    其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

    通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理。

    Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象。

    这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常。

    我们再来看一下同步方法的反编译结果:

    源代码:

    package com.paddx.test.concurrent;
    
    public class SynchronizedMethod {
      public synchronized void method() {
        System.out.println("Hello World!");
      }
    }
    

    反编译结果:
    在这里插入图片描述
    从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现)。不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。

    JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置。

    如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

    其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

    3、运行结果解释

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

    3.1、代码段2结果

    虽然method1和method2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的。

    所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。

    3.2、代码段3结果

    虽然test和test2属于不同对象,但是test和test2属于同一个类的不同实例

    由于method1和method2都属于静态同步方法,所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),所以也只能顺序的执行。

    3.3、代码段4结果

    对于代码块的同步,实质上需要获取Synchronized关键字后面括号中对象的monitor。

    由于这段代码中括号的内容都是this,而method1和method2又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。

    4、总结

    Synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。

    但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用Synchronized关键字。

    另一方面也能够帮助我们更好的理解并发编程机制,有助我们在不同的情况下选择更优的并发策略来完成任务。对平时遇到的各种并发问题,也能够从容的应对。

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,152
精华内容 860
关键字:

synchronized底层原理