Java存在两种锁机制:synchronized和lock
synchronized
介绍
synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,通过对对象的头文件来操作,从而达到加锁和释放锁的目的。当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。
通常,对于synchronize(lock){…}这样的代码块,编译后会生成monitorenter和monitorexit指令,线程执行到monitorenter指令时会尝试取得lock对应的monitor的所有权(CAS设置对象头),取得后即获取到锁,执行monitorexit指令时会释放monitor的所有权即释放锁;
底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。
偏向锁—>轻量锁—>重量锁
悲观锁
Synchronized采用了悲观锁的机制,顾名思义,它假设每次执行同步的时候,别的线程总会也去执行,所以每次都会上锁。线程获得的是独占锁,意味着其他线程只能依靠阻塞来等待占有线程来释放锁。而CPU阻塞和唤醒线程会引起上下文切换,大量线程竞争锁的时候,导致低效。
使用
synchronized可以对整个方法同步,也可以对方法的部分代码块进行同步。当synchronized修饰方法的时候,synchronized是对象级的同步,意思就是说对于某个对象里面的被synchronized修饰的多个方法和synchronized(this)的代码块,当某一个线程访问一个synchronized修饰的方法或执行synchronized(this)的代码块,其它线程访问该对象内的synchronized修饰的方法或执行synchronized(this)的代码块将会处于等待状态,当之前得到锁的线程执行完方法或代码块时,其它线程才可以访问。
使用场景
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好
lock
介绍
lock是基于jdk层面实现的接口,和虚拟机层面不是一个概念;在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReenTrantLock、ReadWriteLock(实现类有ReenTrantReadWriteLock),其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类(简称AQS)
乐观锁
Lock使用了乐观锁的方式,顾名思义,就是每次不加锁而是假设没有冲突而去完成操作。它是基于CAS操作的(Compare And Swap),通过竞争Lock内置队列同步器(AQS)中的状态位state,填充入自己的线程来获得锁。 现代CPU提供了指令,可以自动更新共享数据,而且能够检测其他线程的干扰,而compareAndSet()就用这些代替了锁定,这是一个自旋而非阻塞的算法,不会被挂起或者影响其他线程。
使用
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
使用场景
由于ReentrantLock但是当同步非常激烈的时候,还能维持常态。所以比较适合高并发的场景。
区别
synchronized
优点:实现简单,语义清晰,便于JVM堆栈跟踪,加锁解锁过程由JVM自动控制,提供了多种优化方案,使用更广泛
缺点:悲观的排他锁,不能进行高级功能
lock
优点:可定时的、可轮询的与可中断的锁获取操作,提供了读写锁、公平锁和非公平锁
缺点:需手动释放锁unlock,不适合JVM进行堆栈跟踪
不同点
synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
相同点
都是可重入锁
什么时候选择用 ReentrantLock 代替 synchronized
摘自于IBM网站上的一篇文章:
在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。