简介

当多个线程需要访问某个公共资源的时候,我们知道需要通过加锁来保证资源的访问不会出问题。java提供了两种方式来加锁:

  • 一种是关键字:synchronized,一种是concurrent包下的lock锁

  • synchronized是java底层支持的,而concurrent包则是jdk实现。

公平锁和非公平锁

  • 当有线程竞争锁时,当前线程会首先尝试获得锁而不是在队列中进行排队等候,这对于那些已经在队列中排队的线程来说显得不公平,这也是非公平锁的由来
  • 默认情况下为非公平锁。

锁的存储结构就两个东西:“双向链表” + “int类型状态”。

ReenTrantLock的实现是一种自旋锁, 通过循环调用CAS操作来实现加锁。

它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。想尽办法避免线程进入内核的阻塞状态是我们去分析和理解锁设计的关键钥匙。

ReentrantLock

重要方法

构造方法

public ReentrantLock()
public ReentrantLock(boolean fair)
  • fair:该参数为true时,会尽力维持公平

获得锁

public void lock()
public void lockInterruptibly()  throws InterruptedException
public boolean tryLock()
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException

lock

  • 正常的获取锁,如果没有获得到锁,就会被阻塞

lockInterruptibly

  • 获取锁,如果没有获得到锁,就会被阻塞
  • 可以被打断

tryLock

  • 如果获得到锁,返回true
  • 如果没有获得到锁,返回false
  • timeout:表示等待的时间
  • tryLock()在获取的锁的时候,不会考虑此时是否有其他线程在等待,会破坏公平
  • 如果你希望遵守公平设置此锁,然后用tryLock(0, TimeUnit.SECONDS) 这几乎是等效的(它也检测中断)。

释放锁

public void unlock()
  • 尝试释放此锁。
  • 必须是锁的持有者才能释放锁

锁的调试

protected Thread getOwner()
public final boolean hasQueuedThreads()
public final int getQueueLength()
protected Collection<Thread> getQueuedThreads()

getOwner

  • 返回持有锁的线程

hasQueuedThreads

  • 是否有线程在等待获取锁

getQueueLength

  • 获取等待锁的线程数目

getQueuedThreads

  • 返回正在等待的线程集合

【重点】Lock和synchronized的区别

底层实现

  • Lock基于AQS实现,通过int类型状态CAS机制来维护锁的获取与释放
  • synchronized需要通过monitor经历一个从用户态到内核态的转变过程,更加耗时

其他区别

synchronized Lock
是java内置关键字,在jvm层面 是个java类
无法判断是否获取锁的状态 可以判断是否获取到锁
会自动释放锁 需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁
线程会一直等待下去 如果尝试获取不到锁,线程可以不用一直等待就结束

总结来说
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)