Synchronized和lock的原理和区别

一、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
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
synchronizedlock 都是 Java 中用于实现线程同步的机制,它们的作用都是保证在同一时刻只有一个线程能够访问共享资源。它们的原理有一些不同。 synchronizedJava 语言中的关键字,它是在 JVM 层面上实现的。它的机制是,每个 Java 对象都有一个监视器锁(monitor),当一个线程访问该对象的 synchronized 代码块时,会尝试获取该对象的监视器锁,如果获取到锁,则表示该线程可以进入 synchronized 代码块执行,其他线程则必须等待。当 synchronized 代码块执行完毕时,该线程会释放锁,其他线程则可以尝试获取锁并进入 synchronized 代码块执行。 lockJava 5 中引入的一个接口,它提供了比 synchronized 更加灵活和强大的线程同步机制。lock 接口的实现类可以实现不同的锁定策略,比如可重入锁、公平锁、读写锁等。lock 接口中最常用的实现类是 ReentrantLocklock 的机制是,一个线程在访问共享资源前,必须先获取锁,如果获取不到锁,则该线程会被阻塞,直到获取到锁才能进入临界区访问共享资源。当线程访问完共享资源后,必须释放锁,其他线程才能获取锁访问共享资源。 总的来说,synchronizedJava 的内置机制,使用起来比较简单,但是锁定的粒度比较大,只能锁定整个对象,不能灵活控制锁定范围。而 lock 则是一个接口,使用起来比较灵活,可以实现不同的锁定策略,但是也比较复杂,需要手动加锁和解锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值