Lock的API说明

Lock的API说明

概述

其实觉得"锁",获取"锁"的说法似乎不太符合中国人的思维,这个"锁"其实就是一个"令牌",有令牌就能进入,无令牌就不能进入。这么说来这个"锁"其实应该叫"钥匙"。

当然这里应该还有一个概念,叫房间,这个钥匙是对应什么房间的(锁住什么东西,即锁住什么范围)。所以光叫"钥匙",似乎也不太准确。

所以西方人的这个"锁",应该有两个概念

  • “锁对象”,就是我们说的"钥匙"
  • “锁了什么范围”,就是这个锁锁了什么房间

不过为了概念统一,这里就不纠结这个概念,跟西方的概念保持一致,都叫做"锁"

公平策略

这里简单介绍什么是公平策略,公平策略是针对锁来设定的,不同锁可以设定不同的公平策略。公平策略有 公平不公平

  • 如何设置呢?

在构造 ReentrantLock 的时候,可以传入ReentrantLock(boolean fair),传入后,该锁就是绑定了指定策略了,默认无参构造器不公平

  • 重点来了,什么是公平什么是不公平?

如果线程在获取锁的时候,若锁没有被其他线程占用,能立刻得到锁,这就是不公平,因为可能有若干其他线程在等待链表里等待,结果你一来(一调用某个API获取方法)发现可以获得锁,就立刻得到了,也不进入等待链表排队,这就是不公平。

相反,如果在锁没被其他线程占用,且存在等待链表,当前线程也得加入到等待链表里,这就是公平。

公不公平,不是说同样在等待列表里,如果锁释放,就优先挑这个线程!!!这个是一定要注意的。

如果是非公平锁,遇到锁未释放,还是要进入等待链表的,下次锁释放的时候,并不是优先挑当前的线程。这种公不公平,体现在当前锁是否释放的时机

那有人会说,这个时机的时间不是很短吗?哪里这么巧当前线程调用不公平获取锁的时候(就是调用API时)刚好就锁不占用,锁不是分分钟就被其他等待链表里的线程占用的吗? 还真的不是,空档期说不定还真的很长(对于CPU来说)

  • 为什么默认使用非公平锁

因为非公平锁比公平锁效率更高、吞吐量更大。因为非公平锁每次在获取锁的时候不需要判断等待链表里是否有其他线程。而公平锁必须检查是否有其他节点,应为等待链表是按照FIFO原则的,这样就会有一些损耗,可能经常发生等待线程被唤醒却发现自己不是链表头。因此,非公平锁的吞吐量更高。

关于打断

有些方法获取锁的过程可以被打断也不会中断,有些方法被打断后就中断了,不会再获取。

ReentrantLock

概述

(无)

API

1、lock()

public void lock() 获取锁

  • 接口不返回任何值
  • 如果没有其他线程占用该锁,则立即获取该锁,并设置锁的持有数为1
  • 如果当前线程已经获取了该锁,可以重新再获取一次(可重入),但是锁的持有数加1
  • 如果锁被其他线程持有,则该线程进入休眠等待,直到获取到锁为止,获取后设置锁的持有数为1
  • 锁的公平策略:决定于构造器传入的参数,默认不公平,public ReentrantLock(boolean fair)
  • 不会被中断,不会抛出InterruptedException
这里有些英语比较难理解,但是知道意思即可,如:
If the lock is held by another thread then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock has been acquired,at which time the lock hold count is set to one.
首先:
for thread scheduling purposes,意思是:"为了"线程调度的目的,当前线程要becomes disabled,for...purpose是为了什么目的之意

其次:lies dormant,的lies应该是"躺着、保持着"并非"说谎",整体就是保持休眠状态

最后,at which time the lock hold count is set to one,是修饰前面的"直到取到锁的时候"(现在完成时),即"在这个时候,锁的持有数会被设置成1",是这个意思。
     
/**
 * Acquires the lock.
 *
 * <p>Acquires the lock if it is not held by another thread and returns
 * immediately, setting the lock hold count to one.
 *
 * <p>If the current thread already holds the lock then the hold
 * count is incremented by one and the method returns immediately.
 *
 * <p>If the lock is held by another thread then the
 * current thread becomes disabled for thread scheduling
 * purposes and lies dormant until the lock has been acquired,
 * at which time the lock hold count is set to one.
 */
public void lock() {
    sync.lock();
}

2、tryLock()

public boolean tryLock() 尝试获得锁(其实是"立即获得锁")

  • 立即获取锁(当前被调用时刻),他跟 lock() 的最大区别是不等,当前能获取就获取,不能就拉倒
  • 如果当前线程已经持有该锁,则返回true,并且对锁持有数加1
  • 如果锁正在被占用,返回false
  • 忽略公平原则:比如目前有几个线程在等待这个锁,当持有的线程释放了,tryLock()的线程一定获得锁,即使设置了公平策略,也会忽略。
  • 这个"非公平获取锁"的行为在某些情况是有用的,即使它打破了公平原则。如果你想公平,使用 tryLock(0,TimeUnit.SECONDS)
    • 不会被中断,不会抛出InterruptedException
难理解的英文帮助:
barging behavior:就理解为behavior即可,忽略barging单词,有时候就得这么忽略老外的词,因为他们很啰嗦并且脑壳奇特。如果硬要分析这个单词,它带了双引号,要么是强调的意思,要么是有点意思的比喻或打比方

补充:后来查到有个 queue-barging 词表示插队的意思,那barging在这里应该就是插队的意思了


If you want to honor the fairness setting for this lock:
honor在这里是动词,但似乎不是"尊敬谁给谁荣耀"的意思,"履行",这个意思更加接近。整句话的意思是,"如果你想要履行对这个锁的公平设置",即"如果你想要公平设置生效"




/**
 * Acquires the lock only if it is not held by another thread at the time
 * of invocation.
 *
 * <p>Acquires the lock if it is not held by another thread and
 * returns immediately with the value {@code true}, setting the
 * lock hold count to one. Even when this lock has been set to use a
 * fair ordering policy, a call to {@code tryLock()} <em>will</em>
 * immediately acquire the lock if it is available, whether or not
 * other threads are currently waiting for the lock.
 * This &quot;barging&quot; behavior can be useful in certain
 * circumstances, even though it breaks fairness. If you want to honor
 * the fairness setting for this lock, then use
 * {@link #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS) }
 * which is almost equivalent (it also detects interruption).
 *
 * <p>If the current thread already holds this lock then the hold
 * count is incremented by one and the method returns {@code true}.
 *
 * <p>If the lock is held by another thread then this method will return
 * immediately with the value {@code false}.
 *
 * @return {@code true} if the lock was free and was acquired by the
 *         current thread, or the lock was already held by the current
 *         thread; and {@code false} otherwise
 */
public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

3、tryLock(timeout, unit)

public boolean tryLock(long timeout, TimeUnit unit) 设定超时

  • tryLock() 不同的地方是它在锁被占用时会等一段时间

  • 如果timeout设置为<=0,则方法不等待

    tryLock(0,TimeUnit.SECONDS)的源码显式不等价于无参的 tryLock()的,因为各自调用的sync方法不同。

    但,是在 tryLock()的注释中写了 which is almost equivalent (it also detects interruption),即基本上是等价的(我觉得此时不同点应该是对待公平策略的差异,此问题待确认

  • tryLock()最大的不同点是关于公平的问题,如果设置了公平锁,假如现在释放了锁,它和一群等待中的线程无差别,它也得遵循遵循原则。

  • 如果当前线程已经获取了锁,返回true,并对锁的持有数加1

  • 如果锁正在被别的线程占用,则当前线程进入等待,直到如下三种情况之一发生

    • 其他线程未占用了(如果设置了公平策略还需要公平被抽中),则获取锁,返回true,并设置锁的持有数为1
    • 超时了,获取失败,返回false
    • 被别的线程中断了,抛出InterruptedException
  • 会被中断,可能会抛出InterruptedException(lock()和tryLock()不会)

  • 神奇用法,如果你既想要不公平,又想要设置timeout,则这么使用

if (lock.tryLock() ||
    lock.tryLock(timeout, unit)) {
...
}
翻译解释:
Acquires the lock if it is not held by another thread within the given waiting time and the current thread has not been interrupted.
这句话相当难理解,是以下哪种意思
A 在规定等待时间内,如果锁未被其他线程占用则获取锁,当前线程不会被打断
B 在规定等待时间内,如果当前线程未被打断且锁未被其他线程占用,则获取锁
这里是B的含义,within the given waiting time and the current thread has not been interrupted 是完整的一句话不能断开,and是且的意思,这里用现在完成时的被动,是因为要表达"从获取到最大等待时间期间,**从未**被打断",它想表达的意思是**从未**


If this lock has been set to use a fair ordering policy then an available lock will not be acquired if any other threads are waiting for the lock.
它这里为什么这么肯定如果其他线程在等这个锁的话当前线程一定不会获取到锁呢?(will not be acquired),那只能理解为:有其他线程在等,当前线程要排队,可能按照先来先分配的原则,所以这一次一定不会轮到它  (**待确认**/**
 * Acquires the lock if it is not held by another thread within the given
 * waiting time and the current thread has not been
 * {@linkplain Thread#interrupt interrupted}.
 *
 * <p>Acquires the lock if it is not held by another thread and returns
 * immediately with the value {@code true}, setting the lock hold count
 * to one. If this lock has been set to use a fair ordering policy then
 * an available lock <em>will not</em> be acquired if any other threads
 * are waiting for the lock. This is in contrast to the {@link #tryLock()}
 * method. If you want a timed {@code tryLock} that does permit barging on
 * a fair lock then combine the timed and un-timed forms together:
 *
 *  <pre> {@code
 * if (lock.tryLock() ||
 *     lock.tryLock(timeout, unit)) {
 *   ...
 * }}</pre>
 *
 * <p>If the current thread
 * already holds this lock then the hold count is incremented by one and
 * the method returns {@code true}.
 *
 * <p>If the lock is held by another thread then the
 * current thread becomes disabled for thread scheduling
 * purposes and lies dormant until one of three things happens:
 *
 * <ul>
 *
 * <li>The lock is acquired by the current thread; or
 *
 * <li>Some other thread {@linkplain Thread#interrupt interrupts}
 * the current thread; or
 *
 * <li>The specified waiting time elapses
 *
 * </ul>
 *
 * <p>If the lock is acquired then the value {@code true} is returned and
 * the lock hold count is set to one.
 *
 * <p>If the current thread:
 *
 * <ul>
 *
 * <li>has its interrupted status set on entry to this method; or
 *
 * <li>is {@linkplain Thread#interrupt interrupted} while
 * acquiring the lock,
 *
 * </ul>
 * then {@link InterruptedException} is thrown and the current thread's
 * interrupted status is cleared.
 *
 * <p>If the specified waiting time elapses then the value {@code false}
 * is returned.  If the time is less than or equal to zero, the method
 * will not wait at all.
 *
 * <p>In this implementation, as this method is an explicit
 * interruption point, preference is given to responding to the
 * interrupt over normal or reentrant acquisition of the lock, and
 * over reporting the elapse of the waiting time.
 *
 * @param timeout the time to wait for the lock
 * @param unit the time unit of the timeout argument
 * @return {@code true} if the lock was free and was acquired by the
 *         current thread, or the lock was already held by the current
 *         thread; and {@code false} if the waiting time elapsed before
 *         the lock could be acquired
 * @throws InterruptedException if the current thread is interrupted
 * @throws NullPointerException if the time unit is null
 */
public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

4、unlock()

public void unlock() 释放锁

  • 无返回值
  • 如果当前线程是锁的所有者,释放锁,锁的持有数减1(不一定减后就是0,因为可重入,当前这次unlock不一定减后为0)
  • 如果当前线程是锁的所有者,如果当前锁的持有数已经是0,再unlock就抛IllegalMonitorStateException,因为如果是0,就表示当前线程不持有这个锁,不是这个锁的持有者去unlock()就会抛出异常。
翻译解释:
hold count is decremented:持有数聚递减,这里没说递减多少,递减步长是1

If the hold count is now zero then the lock is released.
机械地翻译这句话就是
如果现在的持有数是0,锁就会被释放。/ 如果现在的持有数是0,锁就是释放状态。

但是这句话是承接上一句话的,即:如果当前线程是这个锁的拥有者,持有数就减1,如果减到0,锁就释放了。

所以翻译成:"如果减到0,锁就被释放"是最合理的


/**
 * Attempts to release this lock.
 *
 * <p>If the current thread is the holder of this lock then the hold
 * count is decremented.  If the hold count is now zero then the lock
 * is released.  If the current thread is not the holder of this
 * lock then {@link IllegalMonitorStateException} is thrown.
 *
 * @throws IllegalMonitorStateException if the current thread does not
 *         hold this lock
 */
public void unlock() {
    sync.release(1);
}

5、newCondition()

public Condition newCondition(),返回关联这个锁的Condition实例

  • 返回关联这个锁的Condition实例
  • 可以多次调用得到多个Condition实例
  • Condition的作用类似于线程间的通信wait()notify()notifyAll()
/**
 * Returns a {@link Condition} instance for use with this
 * {@link Lock} instance.
 *
 * <p>The returned {@link Condition} instance supports the same
 * usages as do the {@link Object} monitor methods ({@link
 * Object#wait() wait}, {@link Object#notify notify}, and {@link
 * Object#notifyAll notifyAll}) when used with the built-in
 * monitor lock.
 *
 * <ul>
 *
 * <li>If this lock is not held when any of the {@link Condition}
 * {@linkplain Condition#await() waiting} or {@linkplain
 * Condition#signal signalling} methods are called, then an {@link
 * IllegalMonitorStateException} is thrown.
 *
 * <li>When the condition {@linkplain Condition#await() waiting}
 * methods are called the lock is released and, before they
 * return, the lock is reacquired and the lock hold count restored
 * to what it was when the method was called.
 *
 * <li>If a thread is {@linkplain Thread#interrupt interrupted}
 * while waiting then the wait will terminate, an {@link
 * InterruptedException} will be thrown, and the thread's
 * interrupted status will be cleared.
 *
 * <li> Waiting threads are signalled in FIFO order.
 *
 * <li>The ordering of lock reacquisition for threads returning
 * from waiting methods is the same as for threads initially
 * acquiring the lock, which is in the default case not specified,
 * but for <em>fair</em> locks favors those threads that have been
 * waiting the longest.
 *
 * </ul>
 *
 * @return the Condition object
 */
public Condition newCondition() {
    return sync.newCondition();
}

6、getHoldCount()

public int getHoldCount() 获取持有数

  • 获取这个锁被持有数
  • 不是持有的线程数,同个线程可以重入,重入依然累加持有数
/**
 * Queries the number of holds on this lock by the current thread.
 *
 * <p>A thread has a hold on a lock for each lock action that is not
 * matched by an unlock action.
 *
 * <p>The hold count information is typically only used for testing and
 * debugging purposes. For example, if a certain section of code should
 * not be entered with the lock already held then we can assert that
 * fact:
 *
 *  <pre> {@code
 * class X {
 *   ReentrantLock lock = new ReentrantLock();
 *   // ...
 *   public void m() {
 *     assert lock.getHoldCount() == 0;
 *     lock.lock();
 *     try {
 *       // ... method body
 *     } finally {
 *       lock.unlock();
 *     }
 *   }
 * }}</pre>
 *
 * @return the number of holds on this lock by the current thread,
 *         or zero if this lock is not held by the current thread
 */
public int getHoldCount() {
    return sync.getHoldCount();
}

7、isFair()

public final boolean isFair() 判断是否公平锁

8、isHeldByCurrentThread()

public boolean isHeldByCurrentThread() 判断当前线程是否持有该锁

  • 感觉等价于 getHoldCount() >= 1,但实际上源码内部不是这么实现的
  • 源码的解释里有 is typically used for debugging and testing,这么一说难道性能比较低建议在debug或测试的时候用。应该不是性能低,注释只是陈述一个最经典的会使用到该方法的场景
/**
 * Queries if this lock is held by the current thread.
 *
 * <p>Analogous to the {@link Thread#holdsLock(Object)} method for
 * built-in monitor locks, this method is typically used for
 * debugging and testing. For example, a method that should only be
 * called while a lock is held can assert that this is the case:
 *
 *  <pre> {@code
 * class X {
 *   ReentrantLock lock = new ReentrantLock();
 *   // ...
 *
 *   public void m() {
 *       assert lock.isHeldByCurrentThread();
 *       // ... method body
 *   }
 * }}</pre>
 *
 * <p>It can also be used to ensure that a reentrant lock is used
 * in a non-reentrant manner, for example:
 *
 *  <pre> {@code
 * class X {
 *   ReentrantLock lock = new ReentrantLock();
 *   // ...
 *
 *   public void m() {
 *       assert !lock.isHeldByCurrentThread();
 *       lock.lock();
 *       try {
 *           // ... method body
 *       } finally {
 *           lock.unlock();
 *       }
 *   }
 * }}</pre>
 *
 * @return {@code true} if current thread holds this lock and
 *         {@code false} otherwise
 */
public boolean isHeldByCurrentThread() {
    return sync.isHeldExclusively();
}

ReentrantReadWriteLock

概述

ReentrantReadWriteLock 是接口 ReadWriteLock 的实现类,跟ReentrantLock一样,也是可重入的,不同点是其区分读锁和写锁,由编程者自行选择要加读锁还是写锁,这样在读锁的代码块允许多个线程,而写锁同一时刻仅允许一个线程。

  • 公平性:跟ReentrantLock一样,默认不公平,也支持公平和非公平
  • 可重入:跟ReentrantLock一样,允许一个线程多次获取锁
  • 读写分离:当线程获得写锁后,其他线程不能获得写锁、读锁;当线程获得读锁,其他线程可以获得读锁但不能获得写锁。

规则是:我获得写锁,别人自然不能获得写锁,不然同时写乱套了,这个好理解。问题是别人也不能获得读锁,因为我正在写,如果你在读那就可能读到乱套的数据;我获得读锁,别人当然可以获得读锁,不然怎么提高效率,但是别人不能获得写锁,我正在读呢,被人获得写锁在写数据我不就读了有问题的数据了吗? 所以它的规则还是比较好理解的!

  • 锁降级:一个线程获得写锁后,再次获取读锁,然后释放写锁,这就是降级。允许降,不允许升,因为升级会带来竞争问题。

  • ReadWriteLock接口不继承Lock接口,ReentrantReadWriteLock类也不继承ReentrantLock类。ReentrantReadWriteLock类实现ReadWriteLock接口,ReentrantLock实现Lock接口。可以说两者从代码上是平行线无关联

  • 问题:我为什么要区分读锁和写锁,我用ReentrantLock也可以啊,在读的情况下代码不加锁不就可以了,干嘛还这么麻烦要使用ReentrantReadWriteLock?

因为两者还是有很大的区别的,假设有个get()方法读取共享数据,put()是写这个共享数据。如果使用ReentrantLock,对于get()不用lock()而对于put()使用lock(),则会出现get()的时候无法保证不被其他线程修改。而使用ReentrantReadWriteLock,对get()上读锁,对put()上写锁,则如果有个线程正在get(),则别的线程是不能put()的,因为获取了读锁就不能获取写锁。两者是有很明显的区别的,详细查看读写锁的获取规则。

API

lock() - 写锁

rwLock.writeLock().lock() public void lock() 获取写锁

  • 如果读锁和写锁都未被其他线程占用,才能获取锁
  • 成功获取锁后将写锁的持有数(write lock hold count)改成1
  • 如果当前线程已经获取了该锁,写锁持有数加1,可重入
  • 如果获取不到锁,就进入等待链表进行等待
  • 该方法不会被中断
  • 默认是非公平策略

总结

上面说得比较零散,这里需要归结下

关于ReentrantLock的API说明:

new ReentrantLock() 时默认使用非公平策略。

猜你喜欢

转载自blog.csdn.net/w8y56f/article/details/89554309