java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java synchronized

深入讲解Java synchronized的核心原理

作者:我是小趴菜

这篇文章主要为大家详细介绍了Java中synchronized的核心原理以及简单的用法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

前言

在此之前先有几个面试题,看大家能答对几题

1.1: 标准访问ab二个线程,是先打印t1还是t2

public class SyncUnit {
    public synchronized void t1() {
        System.out.println("t1");
    }
    public synchronized void t2() {
        System.out.println("t2");
    }
    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit.t2();
        }).start();
    }
}

1.2: t1方法暂停3秒钟,是先打印t1还是t2

public class SyncUnit {
    public synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }
    public synchronized void t2() {
        System.out.println("t2");
    }
    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit.t2();
        }).start();
    }

1.3: 新增一个普通方法hello(),是先打印t1还是hello

public class SyncUnit {
    public synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }
    public synchronized void t2() {
        System.out.println("t2");
    }
    public void hello() {
        System.out.println("hello");
    }
    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit.hello();
        }).start();
    }
}

1.4: 现在有二个SyncUnit对象,是先打印t1还是t2

public class SyncUnit {
    public synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }
    public synchronized void t2() {
        System.out.println("t2");
    }
    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        SyncUnit syncUnit1 = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit1.t2();
        }).start();
    }

1.5: 二个静态同步方法,一个SuncUnit对象,是先打印t1还是t2

public class SyncUnit {
    public static synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }
    public static synchronized void t2() {
        System.out.println("t2");
    }
    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit.t2();
        }).start();
    }
}

1.6: 二个静态同步方法,二个SyncUnit对象,是先打印t1还是t2

public class SyncUnit {
    public static synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }
    public static synchronized void t2() {
        System.out.println("t2");
    }
    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        SyncUnit syncUnit1 = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit1.t2();
        }).start();
    }
}

1.7: 一个静态同步方法,普通同步方法,一个SyncUnit对象,是先打印t1还是t2

public class SyncUnit {
    public static synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }
    public synchronized void t2() {
        System.out.println("t2");
    }
    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit.t2();
        }).start();
    }
}

1.8 一个静态同步方法,普通同步方法,二个SyncUnit对象,是先打印t1还是t2

public class SyncUnit {
    public static synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }
    public synchronized void t2() {
        System.out.println("t2");
    }
    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        SyncUnit syncUnit1 = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit1.t2();
        }).start();
    }
}

synchronized用法

synchronized是java提供的一种解决多线程并发安全的一种内置锁,尽管在jdk1.5之前还被大家吐槽性能问题,但是在1.5之后对synchronized不断的优化,在单机程序中,当设计多线程并发问题时,我们完全可以使用synchronized解决

同步实例方法

 public synchronized void method() {
      //方法逻辑
 }

当synchronized修饰的是一个普通方法的时候,相当于对this对象加锁,一个实例是可以创建多个对象的,所以可以拥有多把锁,就比如下面这个例子,我们创建了二个对象,那就是二把不同的锁,所以在调用t1()的时候,t2()方法由于是不同的锁,所以会直接执行方法

public class SyncUnit {
        public synchronized void t1(){
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1");
        }
        public synchronized void t2() {
            System.out.println("t2");
        }
        public static void main(String[] args) throws Exception{
            SyncUnit syncUnit = new SyncUnit();
            SyncUnit syncUnit1 = new SyncUnit();
            new Thread(() -> {
                syncUnit.t1();
            }).start();
            Thread.sleep(100);
            new Thread(() -> {
               syncUnit1.t2();
            }).start();
        }
    }

同步静态方法

 public static synchronized void method() {
     //方法逻辑
 }

当synchronized修饰的是一个静态方法的时候,相当于对当前实例加锁,一个类只有一个实例,所以无论你创建多少个对象,都只有一把锁,比如下面这个例子,虽然创建了二个不同的对象,但是实际只有一把锁,所以是先打印t1(),然后在打印t2(),因为t2()要等待t1()把锁释放掉之后才能获取到锁

   public class SyncUnit {
            public static synchronized void t1(){
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1");
            }
            public static synchronized void t2() {
                System.out.println("t2");
            }
            public static void main(String[] args) throws Exception{
                SyncUnit syncUnit = new SyncUnit();
                SyncUnit syncUnit1 = new SyncUnit();
                new Thread(() -> {
                    syncUnit.t1();
                }).start();
                Thread.sleep(100);
                new Thread(() -> {
                   syncUnit1.t2();
                }).start();
            }
       }

代码块

   public Object object = new Object();
   public void method() {
      synchronized(object) {
         //方法逻辑
      }
   }

这时候的object是一个对象,就相当于在普通方法上添加synchronized,如果是不同的对象,那么就是不同的锁

      public void method() {
          synchronized(Test.class) {
             //方法逻辑
          }
       }

这时候就相当于在静态方法上添加synchronized,也就是对当前实例加锁,一个类只有一个实例

synchronized核心原理

synchornized是基于JVM中的Monitor锁实现的,Java1.5版本之前的synchornized锁性能较低,但是从1.6版本之后,对synchornized进行了大量的优化,引入了锁粗化,锁消除,偏向锁,轻量级锁,适应性自旋等技术来提升synchornized的性能

1.synchornized修饰的是方法

当synchornized修饰的是方法的时候,当前方法会比普通方法多一个ACC_SYNCHRONIZED的标识符

当JVM执行程序的时候,会判断这个方法是否有ACC_SYNCHRONIZED这个标识符,如果有,则当前线程优先获取Monitor对象,同一个时刻只能有一个线程获取到,在当前线程释放Monitor对象之前,其它线程无法获取到同一个Monitor对象,从而保证了同一时刻只能有一个线程进入到被synchornized修饰的方法

2.synchornized修饰的是代码块

当synchornized修饰的是代码块的时候,synchornized关键字会被编译成monitorentermonitorexit,使得同一时刻只能有一个线程进入到同步代码块中,但是这里为什么会有二个monitorexit,是因为程序正常退出的时候需要释放锁,在程序异常的时候也要释放锁,所以会对应二个

无论synchornized修饰的是方法还是代码块,底层都是通过JVM调用操作系统的Mutes锁实现的,当线程被阻塞时会被挂起,等待CPU重新调度,这会导致线程在操作系统的用户态和内核态之间切换,影响性能

Monitor锁原理

synchornized低成是基于Monitor锁来实现的,而Monitor锁是基于操作系统的Mutex锁实现的,Mutex锁是操作系统级别的重量级锁,所以性能较低

在Java中,创建的任何一个对象在JVM中都会关联一个Monitor对象,所以说任何一个对象都可以成为锁。

在HotSpot JVM中,Monitor是由ObjectMoitor实现的,在ObjectMonitor对象的数据结构中,有几个重要的属性

所以当多个线程同时访问被synchornized修饰的方法或者代码块时候,synchornized加锁和释放锁的底层实现流程大致为:

偏向锁

虽然在程序的方法中或代码块中添加了synchornized,但是在大部分的情况下,不会存在多线程竞争这种情况,并且会出现同一个线程多次获取同一把锁的现象,为了提升这种情况下程序的性能,引入了偏向锁

轻量级锁

当多线程竞争锁不激烈时,可以通过CAS机制竞争锁,这就是轻量级锁,引入轻量级锁的目的是在多线程竞争锁不激烈时,避免由于使用操作系统层面的Mutex重量级锁导致性能低下

重量级锁

重量级锁主要是基于操作系统的Mutex锁实现,重量级锁的执行效率较低,处于重量级锁时被阻塞的线程不会消耗CPU资源

锁升级过程

多个线程在争抢synchornized锁时,在某些情况下,会由无锁状态一步步升级为最终的重量级锁,整个升级过程大致包括如下几个步骤

以上就是深入讲解Java synchronized的核心原理的详细内容,更多关于Java synchronized的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文