001-Java基础-锁

锁是Java并发的基础,对于锁我们需要了解以下四个方面:

  • 理解什么是线程安全。
  • 理解最常用的锁Syncronized和ReentrantLock基本的使用方法。
  • 掌握Syncronized和ReentrantLock底层的实现逻辑。
  • Java类库的其它锁。

什么是线程安全

线程安全就是在多线程环境下,保证共享的、可修改状态的正确性。在程序中,状态一般指的就是数据。

线程安全的基本特征:原子性、可见性、有序性。

Syncronized的基本使用方法和底层实现逻辑

Syncronized可以用来修饰代码或者方法,当一个线程通过Syncronized获得当前锁时,其它线程只能等待。

Synchronized利用monitorenter/monitorexit对事项同步语义。

在Java6以前的版本,Monitor的实现完全依赖操作系统内部的互斥锁,是一个无差别的重量级操纵。

后期版本,Java做了大量改造,提供三种不同级别的Monitor实现:偏斜锁、轻量锁、重量级锁。

JVM会根据情况自动选择需要的锁,并且升级。

ReentrantLock的基本使用方法

ReentrantLock的中文名是再入锁,意思是当一个线程试图获取一个它已经获取的锁时,这个获取动作就自动成功,例如下面的代码:

ReentrantLock lock = new ReentrantLock(true);

void methodA(){
    lock.lock(); // 获取锁
    methodB();
    lock.unlock() // 释放锁
}

void methodB(){
    lock.lock(); // 获取锁
    // 其他业务
    lock.unlock();// 释放锁
}

ReentrantLock在创建时可以设置公平性(fairness),当公平性为true时,JVM会倾向于将锁赋予等待时间最久的线程。

相比之下,Synchronized无法设置公平性。

ReentrantLock通过lock()与unlock()实现加锁和解锁。

与Synchronized相比,ReentrantLock可以实现更多复杂的场景,例如:

  • 可以判断是否有线程或者某个特性线程在排队等待锁。
  • 可以相应中断请求。

ReentrantLock可以通过newCondition方法新建一个Condition(条件变量),Condition的用是将wait、notify等操作转换为相应的对象。

Condition最经典的应用场景就是ArrayBlockingQueue,例如下面ArrayBlockingQueue的构造方法,通过一个ReentrantLock创建两个Condition。

/** Condition for waiting takes */
private final Condition notEmpty;

/** Condition for waiting puts */
private final Condition notFull;
 
public ArrayBlockingQueue(int capacity, boolean fair) {
 if (capacity <= 0)
     throw new IllegalArgumentException();
 this.items = new Object[capacity];
 lock = new ReentrantLock(fair);
 notEmpty = lock.newCondition();
 notFull =  lock.newCondition();
}

在take()方法中,通过Condition的await()方法,阻塞线程。

public E take() throws InterruptedException {
 final ReentrantLock lock = this.lock;
 lock.lockInterruptibly();
 try {
     while (count == 0)
         notEmpty.await();
     return dequeue();
 } finally {
     lock.unlock();
 }
}

同理,在enqueue方法中,通过 signal()方法唤醒线程。

Java类库的其它锁

  • ReadWriteLock读写锁。基于读不互斥,写互斥的原理。适用于读多写少的场景。
  • StampedLock优化读写锁。在ReadWriteLock的基础上做了逻辑优化,减少写锁的开销。
上篇001-Java基础-CAS
下篇001-Java基础-并发包(线程池)