Lock重入锁

前言

在并发编程中有两个核心问题, 一个是互斥另一个是同步。在java sdk并发包中,lock就是解决互斥问题,conditon就是解决同步问题。

Lock

lock有三个重要的特性:

  1. 能够响应中断: 线程在没有获取锁的时候,进入阻塞状态。我们能够给进入阻塞状态的线程发送信号,重新唤醒它。
  2. 支持超时: 在无法获取锁的时候,就等待一定的时间。超时就自动返回。
  3. 非阻塞的获取锁:如果无法获取锁就马上返回。
    以下为它的API
// 支持中断的 API
void lockInterruptibly() 
  throws InterruptedException;
// 支持超时的 API
boolean tryLock(long time, TimeUnit unit) 
  throws InterruptedException;
// 支持非阻塞获取锁的 API
boolean tryLock();

lock–内存可见性问题

可以先看代码

class X {
  private final Lock rtl =
  new ReentrantLock();
  int value;
  public void addOne() {
    // 获取锁
    rtl.lock();  
    try {
      value+=1;
    } finally {
      // 保证锁能释放
      rtl.unlock();
    }
  }
}

这段代码,如果T1线程将value改为1 那么t2线程在执行的时候能够看见吗?
答案是肯定的。
它是利用了volatile相关的happes-before规则。java sdk里面的ReentrantLock,内部持有一个volatile的成员变量state,获取锁的时候,会读写 state的值;解锁的时候,也会读写state,在执行value+=1之后,又读写了一次volatile变量state。根据相关的Happens-before规则:

  1. 顺序性执行: 对于线程T1, value+=1 Happens-before释放锁的操作unlock()。
  2. volatile变量规则: 由于state= 1 会先读取state,所以线程T1的unlock() 操作Happens-before()线程T2的lock()操作。
  3. 传递性规则:线程T1的value+=1 Happens-Before线程T2的lock()操作。
class SampleLock {
  volatile int state;
  // 加锁
  lock() {
    // 省略代码无数
    state = 1;
  }
  // 解锁
  unlock() {
    // 省略代码无数
    state = 0;
  }
}

lock—重入锁

ReentrantLock,指的是线程可以重复获取同一把锁。例如下面的代码,当线程T1执行到1处时,已经获取到了锁rtl,当在1处调用get()方法时,会在2处再次对锁rtl执行加锁操作。如果锁rtl是可重入的,那么线程T1可以再次加锁成功。如果锁rtl是不可重入的,那么线程T1此时会被阻塞。

class X {
  private final Lock rtl =
  new ReentrantLock();
  int value;
  public int get() {
    // 获取锁
    rtl.lock();         ②
    try {
      return value;
    } finally {
      // 保证锁能释放
      rtl.unlock();
    }
  }
  public void addOne() {
    // 获取锁
    rtl.lock();  
    try {
      value = 1 + get(); ①
    } finally {
      // 保证锁能释放
      rtl.unlock();
    }
  }
}

LOCK公平锁与非公平锁

ReentrantLock有两个构造方法。一个是无参构造,一个是传入fair。fair参数代表的是锁的公平策略,如果传入 true就表示需要构造一个公平锁,反之则表示构造一个非公平锁。

// 无参构造函数:默认非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
// 根据公平策略参数创建锁
public ReentrantLock(boolean fair){
    sync = fair ? new FairSync() 
                : new NonfairSync();
}

公平锁就是先等待先唤醒,非公平锁是随机唤醒。

锁的使用经验

  1. 永远只在更新对象的成员变量时加锁
  2. 永远只在访问可变的成员变量
  3. 永远不在调用其他对象的方法时加锁

这三条中最后一条主要是担心在调用慢方法,如长时间IO或者业务复杂。 也会担心其他方法里面本身有锁,在调用的时候加锁会出现双重锁, 容易导致死锁。

posted @   沧海红心_田帅  阅读(14)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示