Synchronized和lock的原理和区别

  • Post author:
  • Post category:其他




一、Synchronized



1、synchronized锁的分类:


1.1、方法级的同步锁



非静态同步方法锁的是当前对象

public class MyLock {

    public static void main(String[] args) throws InterruptedException {

        Phone phone = new Phone();

         new Thread(()->{
             phone.sendEmail();
         },"t1").start();

         Thread.sleep(100L);

         new Thread(()->{
             phone.sendSMS();
         },"t2").start();

          /*for (int i = 0; i < 10; i++) {
                 new Thread(()->{
                     phone.sendSMS();
                 },Thread.currentThread().getName()+i).start();
           }*/
    }
}

class Phone{

    public Phone(){

    }

    public synchronized void sendSMS(){
        System.out.println(Thread.currentThread().getName()+"发送短信");
    }

    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName()+"发送邮件");
        try {
            Thread.sleep(100000000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果: 由于phone实例对象是同一个,所以调用sendEmail后,实例对像就被加锁了,调用这个对象的其他加锁的非静态方法的时候,就会阻塞。

不同的实例对象,是不会相互影响的

这里是引用


类的静态方法

  • 类的静态方法的锁,锁的是Class对象,所以无论是不是同一个实例,都会有锁竞争关系。
  • 静态同步方法(Class对象锁)与非静态同步方法(实例对象锁)之间是不会有锁竞争的(同一实例和不同实例都一样不会有锁竞争)


1.2、代码块级的同步锁



代码级别的锁锁的就是synchronized(lock)括号中的对象



2、synchronized的底层实现:

方法级别的同步无需通过字节码来控制,它使用acc_synchronized访问标志来区分一个方法是不是同步方法,是同步方法,就获取monitor对象,执行方法,然后释放monitor对象。

方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info_Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。

代码级别的同步是通过monitorenter和monitorexit这两个字节码指令来实现的。

代码块的同步是利用monitorenter和monitorexit这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当jvm执行到monitorenter指令时,当前线程试图获取monitor对象的所有权,如果未加锁或者已经被当前线程所持有,就把锁的计数器+1;当执行monitorexit指令时,锁计数器-1;当锁计数器为0时,该锁就被释放了。如果获取monitor对象失败,该线程则会进入阻塞状态,直到其他线程释放锁。



二、Lock



1、Lock的原理:

Lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)

Lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。

Lock释放锁的过程:修改状态值,调整等待链表。 Lock大量使用CAS+自旋。因此根据CAS特性,lock建议使用在低锁冲突的情况下。



三、synchronized和Lock的区别

  1. Lock的加锁和解锁都是由java代码配合native方法(调用操作系统的相关方法)实现的,而synchronize的加锁和解锁的过程是由JVM管理的
  2. 当一个线程使用synchronize获取锁时,若锁被其他线程占用着,那么当前只能被阻塞,直到成功获取锁。而Lock则提供超时锁和可中断等更加灵活的方式,在未能获取锁的条件下提供一种退出的机制。
  3. 一个Lock锁内部可以有多个Condition实例,即有多路条件队列,而synchronize只有一路条件队列;同样Condition也提供灵活的阻塞方式,在未获得通知之前可以通过中断线程以 及设置等待时限等方式退出条件队列。
  4. synchronize对线程的同步仅提供独占模式,而Lock即可以提供独占模式,也可以提供共享模式
  5. synchronized是隐式锁,Lock是显式锁。



四、反编译查看monitorenter和monitorexit


原始的同步代码块的代码

/**
 * 幼儿猿大班
 * 2022/1/13
 */
public class RentrantLock {

    public static void main(String[] args) {
        synchronized ("a"){
            System.out.println("外层");
            synchronized ("a"){
                System.out.println("中层");
            }
        }
    }
}


反编译后的


monitorexit比monitorenter多,多一个minitorexit是为了在异常的时候保证解锁成功

D:\JavaSoftWare\jdk\jdk\jdk1.8.0_141\bin\javap.exe -c G:\project\jiaQiDemo\out\production\jiaQiDemo\com\company\lock\RentrantLock.class
Compiled from "RentrantLock.java"
public class com.company.lock.RentrantLock {
  public com.company.lock.RentrantLock();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String a
       2: dup
       3: astore_1
       4: monitorenter
       5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       8: ldc           #4                  // String 外层
      10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      13: ldc           #2                  // String a
      15: dup
      16: astore_2
      17: monitorenter
      18: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      21: ldc           #6                  // String 中层
      23: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      26: aload_2
      27: monitorexit
      28: goto          36
      31: astore_3
      32: aload_2
      33: monitorexit
      34: aload_3
      35: athrow
      36: aload_1
      37: monitorexit
      38: goto          48
      41: astore        4
      43: aload_1
      44: monitorexit
      45: aload         4
      47: athrow
      48: return
    Exception table:
       from    to  target type
          18    28    31   any
          31    34    31   any
           5    38    41   any
          41    45    41   any
}



版权声明:本文为IT_tyro原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。