《JAVA并发编程实战》第十三章 显示锁

13.1  Lock与ReentrantLock

Reentrant 英[riːˈɛntrənt] 可重入; 可重入的; 重入; 可再入的; 重进入;

程序清单13-1 Lock接口定义
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;

public interface Lock {

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();
}
public class ReentrantLock implements Lock, java.io.Serializable {...}

ReentrantLock提供了与synchronized相同的互斥性和可见性,可重入。
并且与synchronized相比,还为处理锁的不可用性问题提供了更高的灵活性。

为什么要创建一种与内置锁如此类似的新加锁机制?

大多数情况下内置锁都能很好地工作,但在功能上存在一些局限性。eg:

  1. 无法中断一个正在等待获取锁的线程
  2. 无法再请求获取一个锁时无限地等待下去
     (开始请求锁后,这个操作将无法取消,因此内置锁很难实现带有时间限制的操作)
  3. 内置锁无法实现非阻塞结构的加锁机制
程序清单13-2 使用ReentrantLock来保护对象状态
Lock lock = new ReentrantLock();

lock.lock();
try {
    //更新对象状态
    //捕获异常,并在必要时恢复不变性条件
} finally {
    lock.unlock();
}

13.1.1 轮询与定时

程序清单13-3 通过tryLock来避免锁顺序死锁(10.1.2)
public boolean transferMoney(Account fromAccount, Account toAccount, DollarAmount amount,long timeout, TimeUnit unit) {
    long fixedDelay = getFixedDelayComponentNanos(timeout,unit);
    long randMod = getRandomDelayComponentNanos(timeout,unit);
    long stopTime = System.nanoTime() + unit.toNanos(timeout);
    while(true) {
        if(fromAccount.lock.tryLock()) {
            try {
                if(toAccount.lock.tryLock()) {
                    try {
                        if(fromAccount.getBalance().compareTo(amount) < 0) {
                            throw new InsufficientResourcesException();
                        } else {
                            fromAccount.debit(amount);
                            toAccount.credit(amount);
                            return true;
                        }
                    } finally {
                        toAccount.lock.unlock();
                    }
                }
            } finally {
                fromAccount.lock.unlock();
            }
        }
        if(System.nanoTime() < stopTime)
            return false;
        NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
    }
    return false;
}
程序清单13-4 带有时间限制的加锁
//试图在Lock保护的共享通信线路上发送一条消息。
public boolean trySendOnSharedLine(String message,long timeout,TimeUnit unit) {
    long nanoToLock = unit.toNanos(timeout) - estimatedNanosToSend(message);
    if(!lock.tryLock(timeout, unit)) //如果不能在指定时间内完成,代码就会失败。独占加锁行为
        return false;
    try {
        return sendOnSharedLine(message); 
    } finally {
        lock.unlock();
    }
}

13.1.2 可中断的锁获取操作

程序清单13-5 可中断的锁获取操作
public boolean trySendOnSharedLine(String message) throws InterruptedException {
    lock.lockInterruptibly();
    try {
        return cancellableSendOnSharedLine(message); 
    } finally {
        lock.unlock();
    }
}

public boolean cancellableSendOnSharedLine(String message) throws InterruptedException{
    //TODO ....
    return true;
}

定时的tryLock()同样能响应中断,因此当需要实现一个定时的和可中断的锁操作时,可以使用tryLock方法

13.1.3 非块结构的加锁

13.2 性能考虑因数

Java 5.0中 ReentrantLock比内置锁有更好的竞争性能(是可伸缩性的关键要素。体现在锁的管理和调度)
从Java 6.0 开始 则内置锁synchronized 追平了ReentrantLock

13.3 公平性

ReentrantLock 构造函数提供2种选择:

1.非公平的锁(默认):允许【插队】:
  *1)当一个线程请求非公平锁时,如果在发出请求的同时改锁的状态变为可用,
2)只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中
那么这个线程将跳过队列中所有的等待线程并获得这个锁。

3)不提倡插队,但无法防止某个线程在合适的时候进行“插队”
2.公平的锁:线程按照发出请求的顺序来获得锁
新发出请求的线程将被安排在等待队列中,不会插队

为什么不希望所有的锁都是公平的?

  公平性将由于挂起和恢复线程时存在的开销而极大地降低性能。非公平锁的性能要高于公平锁的性能。

A线程持有锁 ——> B在排队等待 ——>A释放锁——>唤醒B——>B请求锁
A线程持有锁 ——> B在排队等待 ——>A释放锁,于此同时C请求锁,C可能在B被唤醒之前获得、使用、释放锁
——>唤醒B——>B请求锁(可能此时C已经执行完了)

什么情况下使用公平锁?

当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,那么应该使用公平锁

13.4 在synchronized和ReentrantLock之间选择

在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock:

  • 可定时的、可轮询的、可中断的锁获取操作
  • 公平队列
  • 非块结构的的锁

否则应该优先使用synchronized

13.5 读 - 写锁

一个资源可以被多个读操作访问, 或者被一个写操作访问,但两者不能同时进行。

程序清单13-6 ReadWriteLock接口
public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}
ReentrantReadWriteLock实现

ReentrantReadWriteLock的读写锁都可重入。
公平锁:等待时间最长的将获得锁。
    读线程持有锁,下一个是写入锁,此时所有的其它读线程都无法获取到锁,必须等待写线程执行完时间片后才行。
非公平锁:线程获取访问许可的顺序不定。
     写线程可以降级为读线程,读线程不可以升级为写线程(会产生死锁)

Listing 13.7. Wrapping a Map with a Readwrite Lock
public class ReadWriteMap {
    private final Map map;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock r = lock.readLock();
    private final Lock w = lock.writeLock();
    public ReadWriteMap(Map map) {
        this.map = map;
}
    public V put(K key, V value) {
        w.lock();
        try {
            return map.put(key, value);
        } finally {
            w.unlock();
} }
    // Do the same for remove(), putAll(), clear()
    public V get(Object key) {
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
} }
    // Do the same for other read-only Map methods

你可能感兴趣的:(《JAVA并发编程实战》第十三章 显示锁)