Java--Synchronized与Lock的区别及底层实现

起初 Java 中只有 synchronized 这一种对程序加锁的方式,因此在JDK1.5之前,我们在编写并发程序的时候无一例外都是使用synchronized来实现线程同步的,而synchronized在JDK1.5之前同步的开销较大效率较低,因此在JDK1.5之后,推出了代码层面的Lock接口(synchronized为jvm层面)来实现与synchronized同样功能的同步锁,并且针对不同的并发场景也加入了许多个性锁功能

一、synchronized

synchronized是Java中的一个关键字,他的实现时基于jvm指令去实现的 

        Object syn = new Object();
        System.out.println("同步开始!");
        synchronized (syn) {//加锁
            //synchronized代码块
            System.out.println("synchronized");
        }//解锁
        System.out.println("同步结束!");

我们运行得到.class文件,然后找到.class文件下,通过终端如下命令javap -v XXXX.class查看

javap -v XXXX.class

如下:

 1、synchronized同步方法时:

如果修饰同步方法是通过的flag ACC_SYNCHRONIZED来完成的,也就是说一旦执行到这个方法,就会先判断是否有标志位,然后ACC_SYNCHRONIZED会去隐式调用刚才的两个指令:monitorenter和monitorexit。

2、synchronized修饰同步代码块时:

首先如果被synchronized修饰在方法块的话,是通过 monitorenter 和 monitorexit 这两个字节码指令获取线程的执行权的。当方法执行完毕退出以后或者出现异常的情况下会自动释放锁。

以上不管修饰哪一种:不管哪一种本质是对一个对象监视器(monitor)进行获取

monitor它就是个监视器,底层源码是C++编写的。在hotspot虚拟机中,它是采用ObjectMonitor类来实现monitor的源码如下:

bool has_monitor() const {
    return ((value() & monitor_value) != 0);
  }
  ObjectMonitor* monitor() const {
    assert(has_monitor(), "check");
    // Use xor instead of &~ to provide one extra tag-bit check.
    return (ObjectMonitor*) (value() ^ monitor_value);
  }

3、synchronized原理

 在Java虚拟机执行到monitorenter指令时,1⃣️首先它会尝试获取对象的锁,如果该对象没有锁,或者当前线程已经拥有了这个对象的锁时,它会把计数器+1;然后当执行到monitorexit 指令时就会将计数器-1;然后当计数器为0时,锁就释放了。2⃣️如果获取锁 失败,那么当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。

但是我们发现图上有2个monitorexit,那是因为synchronized锁释放有两种机制,一种就是执行完释放;另外一种就是发送异常,虚拟机释放。图中第二个monitorexit就是发生异常时执行的流程,这就是我开头说的“会有2个流程存在“。而且,从图中我们也可以看到在第30行,有一个goto指令,也就是说如果正常运行结束会跳转到38行执行。

二、Lock

Lock是java.util.concurrent.Locks 包下的一个接口,定义如下:

public interface Lock {
    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();
}

在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReentrantLock、ReadWriteLock(实现类ReentrantReadWriteLock),其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类简称AQS,他是实现Lock接口所有锁的核心。 

public class LockClass {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();

    public static void main(String args[]){
        final LockClass obj = new LockClass();
        new Thread(() -> obj.insert(Thread.currentThread())).start();
        new Thread(() -> obj.insert(Thread.currentThread())).start();
    }

    private void insert(Thread thread) {
        lock.lock();//加锁
        try {
            System.out.println(thread.getName() + "得到锁");
            for (int i = 0;i < 5;i++){
                arrayList.add(i);
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            System.out.println(thread.getName() + "释放锁");
            lock.unlock();//解锁
        }
    }
}

lock 锁住的是 Lock 对象,当调用它的 lock 方法时,会将 Lock 类中的一个标志位 state 加 1(state 其实是 AbstractQueuedSynchronizer 这个类中的一个变量,它是 Lock 中一个内部类的父类),释放锁时是将 state 减 1(加 1 减 1 这样的操作是为了实现可重入)

三、Synchronized与Lock的区别

类别synchronizedLock
存在层次Java的关键字,在jvm层面上是一个类
锁的释放

1、以获取锁的线程执行完同步代码,释放锁

2、线程执行发生异常,jvm会让线程释放锁

在finally中必须释放锁,不然容易造成线程死锁
锁的获取假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态无法判断可以判断
锁类型可重入 不可中断 非公平可重入 可中断 可公平/非公平(两者皆可)
性能少量同步大量同步
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值