目录

Java 多线程(三) Lock的使用

名词解释

乐观悲观锁

在 Java 中几乎都是悲观锁, 仅存的乐观锁有Atomic(Integer/Boolean)… 等原子类

实现乐观锁用到了 CAS(Compare And Swap: 比较并替换), 乐观锁本身并没有进行加锁操作, 只是不断循环重试的操作

  1. 保存原值为 A

  2. 执行对数据的操作

  3. CAS: 对数据更新时, 更新为 B 之前检查原值是否为 A, 如果是就将原值更新为 B, 否则什么都不干, 这个操作对 CPU 来说是原子性的

乐观锁适用于写比较少的场景, 可以省去锁的开销, 增大了整个系统的吞吐量. 但如果总是产生冲突, 会不断进行重试, 反倒降低了性能, 适合使用悲观锁

死锁

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void thread0(int m) {
    synchronized(lockA) { // 获得lockA的锁
        synchronized(lockB) { // 获得lockB的锁
            // todo
        } // 释放lockB的锁
    } // 释放lockA的锁
}

void thread1(int m) {
    synchronized(lockB) { // 获得lockB的锁
        synchronized(lockA) { // 获得lockA的锁
            // todo
        } // 释放lockA的锁
    } // 释放lockB的锁
}

两个线程各自持有不同的锁,然后各自试图获取对方手里的锁,造成了双方无限等待下去,这就是死锁

解决办法: 线程获取锁的顺序要一致

可重入锁

1
2
3
4
5
6
7
void thread0(int m) {
    synchronized(lockA) { // 获得lockA的锁 记录+1
        synchronized(lockA) { // 记录+1
            // todo
        } // 释放lockA的锁 记录-1
    } // 记录-1
}

JVM 允许同一个线程重复获取同一个锁, 这种能被同一个线程反复获取的锁, 就叫做可重入锁, synchronizedReentrantLock 都是可重入锁

Lock 类

ReentrantLock

表面翻译是可重入锁, 你可以把它当成手动控制的 synchronized

就比如下面的代码:

1
2
3
4
5
void add(int n) {
    synchronized(this) {
        // todo
    }
}

可以替换为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
final Lock lock = new ReentrantLock();

void add(int n) {
    lock.lock();
    try {
        // todo
    } finally {
        lock.unlock();
    }
}

synchronized不同的是,ReentrantLock可以尝试获取锁:

1
2
3
4
5
6
7
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // todo
    } finally {
        lock.unlock();
    }
}

上述代码在尝试获取锁的时候,最多等待1秒。如果1秒后仍未获取到锁,tryLock()返回false,程序就可以做一些额外处理,而不是无限等待下去

公平锁

new 一个 ReentrantLock 的时候参数为 true, 表明实现公平锁机制, 谁等的时间最长, 谁就先获取锁
如果要实现随机获取, 可以传入参数 false(默认)

Condition

Condition提供的await()signal()signalAll()原理和synchronized锁对象的wait()notify()notifyAll()是一致的,并且其行为也是一样的

1
2
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();

ReadWriteLock

  • 只允许一个线程写入 (其他线程既不能写入也不能读取)
  • 没有写入时, 多个线程允许同时读以提高性能
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
final ReadWriteLock rwlock = new ReentrantReadWriteLock();
final Lock rlock = rwlock.readLock();
final Lock wlock = rwlock.writeLock();

void write(int index) {
    wlock.lock(); // 加写锁
    try {
        // todo
    } finally {
        wlock.unlock(); // 释放写锁
    }
}

int read() {
    rlock.lock(); // 加读锁
    try {
        return 0;
    } finally {
        rlock.unlock(); // 释放读锁
    }
}
允许 不允许
不允许 不允许

ref: https://www.liaoxuefeng.com/