您当前的位置: 首页 > 

石头wang

暂无认证

  • 1浏览

    0关注

    295博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Lock的API说明

石头wang 发布时间:2019-04-26 15:26:39 ,浏览量:1

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.
 *
 * 

Acquires the lock if it is not held by another thread and returns * immediately, setting the lock hold count to one. * *

If the current thread already holds the lock then the hold * count is incremented by one and the method returns immediately. * *

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.
 *
 * 

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()} will * immediately acquire the lock if it is available, whether or not * other threads are currently waiting for the lock. * This "barging" 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). * *

If the current thread already holds this lock then the hold * count is incremented by one and the method returns {@code true}. * *

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设置为= 1,但实际上源码内部不是这么实现的

  • 源码的解释里有 is typically used for debugging and testing,这么一说难道性能比较低建议在debug或测试的时候用。应该不是性能低,注释只是陈述一个最经典的会使用到该方法的场景
/**
 * Queries if this lock is held by the current thread.
 *
 * 

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: * *

 {@code
 * class X {
 *   ReentrantLock lock = new ReentrantLock();
 *   // ...
 *
 *   public void m() {
 *       assert lock.isHeldByCurrentThread();
 *       // ... method body
 *   }
 * }}
* *

It can also be used to ensure that a reentrant lock is used * in a non-reentrant manner, for example: * *

 {@code
 * class X {
 *   ReentrantLock lock = new ReentrantLock();
 *   // ...
 *
 *   public void m() {
 *       assert !lock.isHeldByCurrentThread();
 *       lock.lock();
 *       try {
 *           // ... method body
 *       } finally {
 *           lock.unlock();
 *       }
 *   }
 * }}
* * @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() 时默认使用非公平策略。

关注
打赏
1663722529
查看更多评论
立即登录/注册

微信扫码登录

0.0401s