Lock

Lock继synchronized出现的原因

synchronized无法实现破坏不可抢占锁的功能,synchronized申请资源申请不到的时候,直接进入阻塞状态,啥都干不了,也释放不了线程占有的资源;
所以为了解决这个问题,出现了Java SDK——Lock;Lock接口的三个方法如下:

  1. // 支持中断的API
  2. // 能够响应中断。阻塞状态的线程能够响应中断信号,能够唤醒它(那么就有可能释放持有的锁)
  3. void lockInterruptibly() throws InterruptedException;
  4. // 支持超时的API
  5. // 一段时间内没有获取到锁,不是进入阻塞状态,而是返回错误
  6. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  7. // 支持非阻塞获取锁的API
  8. // 尝试获取锁失败,不进入阻塞状态直接返回
  9. boolean tryLock();

如何保证可见性

synchronized之所以能保证可见性是因为Happens-Before中一条规则,”管程中锁的规则。对一个锁的解锁Happens-Before于后续对这个锁的加锁“;
Java SDK里面的锁主要是利用violate相关的Happens-Before规则(顺序性、volatile变量规则、传递性规则),例子如下:

  1. class SampleLock {
  2. volatile int state;
  3. // 加锁
  4. lock() {
  5. // 省略代码无数
  6. state = 1;
  7. }
  8. // 解锁
  9. unlock() {
  10. // 省略代码无数
  11. state = 0;
  12. }
  13. }

可重入锁

ReentrantLock,线程可以重复获取同一把锁;除了可重入锁还有可重入函数,可重入函数指得是多个线程同时调用该函数,每个线程都能得到正确结果;同时在一个线程内支持线程切换。其实就是意味着线程安全;

公平锁和非公平锁

ReentrantLock 这个类有两个构造函数,一个是无参构造函数,一个是传入 fair 参数的构造函数。

  1. //无参构造函数:默认非公平锁
  2. public ReentrantLock() {
  3. sync = new NonfairSync();
  4. }
  5. //根据公平策略参数创建锁
  6. public ReentrantLock(boolean fair) {
  7. sync = fair ? new FairSync() : new NonfairSync();
  8. }

实战示例

利用两个条件变量快速实现阻塞队列

  1. public class BlockedQueue<T>{
  2. final Lock lock = new ReentrantLock();
  3. // 条件变量:队列不满
  4. final Condition notFull = lock.newCondition();
  5. // 条件变量:队列不空
  6. final Condition notEmpty = lock.newCondition();
  7. // 入队
  8. void enq(T x) {
  9. lock.lock();
  10. try {
  11. while (队列已满){
  12. // 等待队列不满
  13. notFull.await();
  14. }
  15. // 省略入队操作...
  16. //入队后,通知可出队
  17. notEmpty.signal();
  18. } finally {
  19. lock.unlock();
  20. }
  21. }
  22. // 出队
  23. void deq(){
  24. lock.lock();
  25. try {
  26. while (队列已空){
  27. // 等待队列不空
  28. notEmpty.await();
  29. }
  30. // 省略出队操作...
  31. //出队后,通知可入队
  32. notFull.signal();
  33. } finally {
  34. lock.unlock();
  35. }
  36. }
  37. }