介绍几种锁的基本使用。

ReentrantLock

Java.util.concurrent.locks.*包中有三个接口:Lock、Condition、ReadWriteLock。两个重要的锁对象:公平锁-ReentrantLock、读写锁-ReentrantReadWriteLock。

并发编程中使用Lock的目的是实现相应的同步效果。

  • ReentrantLock类
  • java.util.concurrent.locks.ReentrantLock类
    Java多线程中,使用synchronized关键字实现线程间的同步和互斥,但ReentrantLock(实现自Lock接口)类也能得到同样的效果,同时还扩展更多功能,包括:嗅探锁定、多路分支通知等。reentrant是可重入的意思,就像Python中的threadin.RLock。可重入锁在同一个线程内获取多次不会锁定。

instance 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package service

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Service {

private Lock lock = new ReentrantLock();

public void test() {
lock.lock();
for (int i = 1; i < 5; i++) {
System.out.println("ThreadName=" + Thread.currentThread().getName() + "num: " + i);
}
lock.unlock();
}
}

  • 使用Condition实现等待/通知
    关键字synchronized与wait和notify/notifyAll方法结合可以实现等待/通知模式。ReentrantLock中的newConditon函数可以创建condition锁对象。类比于Python中的threading.Condition对象

instance 2
condition.await()调用前调用lock.lock方法获得同步监听器。对比Python中的Condition的用法。

1
2
3
4
5
6
7
import threading

def task(condition):
condition.acquire() #调用acquire方法,获得同步监听器
if cond:
#do
condition.wait()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Service {
private Lock lock = new ReentrantLock();
public Condition condition = lock.newCondition();

public void await() {
try {
lock.lock(); //调用lock.lock方法获得同步监听器
System.out.println("await time is " + System.currentTimeMillis());
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signal() {
try {
lock.lock();
System.out.println("signal time is " + System.currentTimeMillis());
condition.signal();
} finally {
lock.unlock();
}
}
}

Java的condition锁中没有像Python中的condition那样通过内置的acquire方法获取同步监听器,所以需要外部设置锁lock,调用unlock方法获取同步监听器。

condition synchronized
await/signal/signalAll wait/nofity/notifyAll

一个综合例子: 生产者/消费者模型:交替打印输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Service {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private boolean hasValue = false;

public void set() {
try {
lock.lock();
while (hasValue) {
System.out.println("print **");
condition.await();
}
System.out.println("print *");
hasValue = true;
condition.signal(); //wait up one thread
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void get() {
try {
lock.lock();
while (hasValue) {
System.out.println("print --");
condition.await();
}
System.out.println("print -");
hasValue = false;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

  • 公平锁/非公平锁
    公平锁是锁分配的顺序按照线程请求锁的顺序确定。类比FIFO。非公平锁则是随机抢占。ReentrantLock类的构造器可以传入一布尔类型确定是否是公平锁。
1
2
3
4
5
6
7
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;

public class Service {
private Lock lock = new ReentrantLock(false); //非公平锁
//...
}

ReentrantLock 中的方法

1
2
3
4
5
6
7
8
9
10
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;

Lock lock = new ReentrantLock();

getHoldCount() 当前线程获取lock的个数,也就是调用lock方法的次数与调用unlock方法次数的差。

getQueueLength() 返回正在等待获取lock的线程数。

getWaitQueueLength(Condition condition) Returns an estimate of the number of threads waiting on the given condition associated with this lock

更多参考Java官方文档

ReentrantReadWriteLock

读写锁表示两个锁,分别是读锁、写锁。读锁也称为共享锁;写锁称为排他锁。于是,多个读锁之间不互斥,但读锁和写锁、写锁和写锁是互斥的。在多线程下,多个线程可以同时读取数据,但只能有一个线程进行写入操作。读锁、写锁的更详细理论参考数据库原理中的读写锁

互斥情况:

  1. 读读共享
  2. 写写互斥
  3. 读写互斥

instance 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.locks.ReentrantReadWriteLock;

public class Service {

private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public void read() {
lock.readLock().lock();
System.out.println("readLock");
lock.readLock().unlock();
}

public void write() {
lock.writeLock().lock();
System.out.println("writeLock");
lock.writeLock().unlock();
}
}