Lock接口
接口代码:
public interface Lock{
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLcok(long timeout,TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
使用方式:
// ReentrantLock是Lock接口的默认实现类
Lock lock = new ReentrantLock();
lock.lock();
try{
...
}finally{
lock.unlock();
}
注意事项:必须在finally块中释放锁。否则在被保护的代码中抛出了异常,那么这个锁永远都无法释放。
ReentrantLock
ReentrantLock既支持公平锁,也支持非公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock默认是非公平锁。
公平锁保障锁调度的公平性往往是以增加了线程的暂停和唤醒的可能性,即增加了上下文切换为代价的。公平锁适合于锁被持有的时间相对长或者线程申请锁的平均间隔时间相对长的情形。
线程切换的开销,其实就是非公平锁效率高于公平锁的原因,因为非公平锁减少了线程挂起的几率,后来的线程有一定几率逃离被挂起的开销。
内部锁仅支持非公平锁。
ReadWriteLock
public interface ReadWriteLock{
Lock readLock();
Lock writeLock();
}
读/写锁:一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行。
读锁对于读线程来说起到保护其访问的共享变量在其访问期间不被修改的作用,并使多个读线程可以同时读取这些变量从而提高了并发性;而写锁保障了写线程能够以独占的方式安全地更新共享变量。写线程对共享变量的更新对读线程是可见的。
锁 | 获得条件 | 排他性 | 作用 |
---|---|---|---|
读锁 | 相应的写锁未被任何线程持有 | 对读线程是共享的,对写线程是排他的 | 允许多个读线程可以同时读取共享变量,并保障读线程读取共享变量期间没有其他任何线程能更新这些变量 |
写锁 | 该写锁未被其他任何线程持有并且相应的 读锁未被其他任何线程持有 | 对写线程和读线程都是排他的 | 使得写线程能够以独占的方式访问共享变量 |
ReentrantLockReadWriteLock
模版代码:
private final ReadWriteLock rwLock = new ReentrantLockReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
// 读线程执行该方法
public void reader(){
// 申请读锁
readLock.lock();
try{
// 再此区域读取共享变量
} finally{
// 总是在finally块中释放锁,以免锁泄漏
readLock.unlock();
}
}
// 写线程执行该方法
public void writer(){
// 申请写锁
writeLock.lock();
try{
// 再此区域写共享变量
} finally{
// 总是在finally块中释放锁,以免锁泄漏
writeLock.unlock();
}
}
代码示例:
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static void main(String[] args) {
final Queue3 q3 = new Queue3();
for (int i = 0; i < 3; i++) {
new Thread() {
@Override
public void run() {
while (true) {
q3.get();
}
}
}.start();
}
for (int i = 0; i < 1; i++) {
new Thread() {
@Override
public void run() {
while (true) {
q3.put(new Random().nextInt(10000));
}
}
}.start();
}
}
}
class Queue3 {
/**
* 共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
*/
private Object data = null;
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public void get() {
rwl.readLock().lock();//上读锁,其他线程只能读不能写
try {
System.out.println(Thread.currentThread().getName() + " be ready to read data!");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " have read data :" + data);
} finally {
// 释放读锁
rwl.readLock().unlock();
}
}
public void put(Object data) {
rwl.writeLock().lock();//上写锁,不允许其他线程读也不允许写
try {
System.out.println(Thread.currentThread().getName() + " be ready to write data!");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
System.out.println(Thread.currentThread().getName() + " have write data: " + data);
} finally {
rwl.writeLock().unlock();//释放写锁
}
}
}
使用场景
- 只读操作比写(更新)操作要频繁得多
- 线程持有锁时间比较长
同时满足上面两个条件的时候,读写锁才是适宜的选择。
ReentrantReadWriteLock所实现的读写锁是个可重入锁。ReentrantReadWriteLock支持锁的降级,即一个线程持有读写锁的写锁的情况下,可以继续获得相应的读锁。ReentrantReadWriteLock并不支持锁的升级。
示例代码:
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheDemo {
/**
* 缓存器
*/
private Map<String, Object> map = new HashMap<>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public Object get(String id) {
Object value = null;
rwl.readLock().lock();//首先开启读锁,从缓存中去取
try {
value = map.get(id);
//如果缓存中没有释放读锁,上写锁
if (value == null) {
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
if (value == null) {
// 此时可以去数据库中查找,这里简单的模拟一下
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = new Random().nextInt(100);
map.put(id, value);
}
} finally {
rwl.writeLock().unlock(); //释放写锁
}
rwl.readLock().lock(); //然后再上读锁
}
} finally {
rwl.readLock().unlock(); //最后释放读锁
}
return value;
}
public static void main(String[] args) throws InterruptedException {
CacheDemo cacheDemo = new CacheDemo();
for (int i = 0; i < 10; i++) {
int a = i;
new Thread() {
@Override
public void run() {
cacheDemo.get(String.valueOf(a));
}
}.start();
}
new Thread() {
@Override
public void run() {
int i = 0;
do {
System.out.println("读:key=" + i + ",value=" + cacheDemo.get(String.valueOf(i)));
i++;
} while (i < 10);
}
}.start();
new Thread() {
@Override
public void run() {
int i = 10;
do {
System.out.println("写:key=" + i + ",value=" + cacheDemo.get(String.valueOf(i)));
i++;
} while (i < 15);
}
}.start();
}
}
Lock接口中的Condition用法
如果想编写一个带有多个条件谓词的并发对象,或者想获得除了条件队列可见性之外的更多控制权,就可以使用显式的Lock和Condition而不是内置锁和条件队列,这是一种更灵活的选择。
一个Condition和一个Lock关联在一起,就像一个条件队列和一个内置锁相关联一样。要创建一个Condition,可以在相关联的Lock上调用Lock.newCondition方法。
与内置条件队列不同的是,对于每个Lcok,可以有任意数量的Condition对象。Condition对象继承了相关的Lock对象的公平性,对于公平的锁,线程会依照FIFO顺序从Condition.await中释放。
代码示例:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionBoundedBuffer<T> {
protected final Lock lock = new ReentrantLock();
/**
* 条件谓词:notFull(coun<items.length)
*/
private final Condition notFull = lock.newCondition();
/**
* 条件谓词:notEmpty(count>0)
*/
private final Condition notEmpty = lock.newCondition();
private static final int BUFFER_SIZE = 100;
private final T[] items = (T[]) new Object[BUFFER_SIZE];
private int tail, head, count;
/**
* 阻塞并直到:notFull
*
* @param x
* @throws InterruptedException
*/
public void put(T x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[tail] = x;
if (++tail == items.length)
tail = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
/**
* 阻塞并直到:notEmpty
*
* @return
* @throws InterruptedException
*/
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
T x = items[head];
items[head] = null;
if (++head == items.length)
head = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
测试:
import java.util.concurrent.TimeUnit;
public class ConditionBoundedBufferTest{
public static void main(String[] args) throws InterruptedException {
ConditionBoundedBuffer<String> list = new ConditionBoundedBuffer<>();
new Thread(){
@Override
public void run(){
try {
list.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
new Thread(){
@Override
public void run(){
try {
list.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
TimeUnit.SECONDS.sleep(2);
new Thread(){
@Override
public void run(){
try {
list.put("yangyun");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
output:
Thread-0 take begin
Thread-0 take while
Thread-1 take begin
Thread-1 take while
Thread-2 put begin
Thread-2 put go on
Thread-0 take go on
Condition.await( )方法会释放锁,所以下一个线程可以进入take( )方法。当其他线程中使用signal( )或者signalAll( )方法时,线程会重新获得锁并继续执行。或者当线程中断时,也能跳出等待。