JAVA并发编程实战-显式锁

思维导图

在这里插入图片描述

1 Lock和ReentrantLock

Lock定义了抽象的锁操作,如下图
在这里插入图片描述

Lock提供了无条件的、可中断的、可轮询的和定时的获取锁操作。
Lock的实现与内部锁具有相同的内存可见性语义。

ReentrantLock实现了Lock接口,提供了与synchronized相同的互斥和内存可见性保证。
内部锁存在的条件下,创建这种Lock机制原因,主要是内部锁具有的一些局限:

  • 不能中断正在等待获取锁的线程,并且请求失败必须无限等待。
  • 不具备高级特性,比如定时、可轮询等。

下列demo-1是Lock接口的规范形式:

    private final Lock lock = new ReentrantLock();
//lock接口规范形式
    public void addPlace(String place) {
        lock.lock();
        try {
            //应用操作
            //捕获异常
        } catch (Exception e) {
            //处理异常
        } finally {
            //finally释放锁
            lock.unlock();
        }
    }
}

Lock锁必须在finally块中显示释放。

1.1 可轮询的和可定时的锁

Lock定义的tryLock的两种形式可以实现可轮询和可定时的锁操作。这些操作可以避免死锁的发生。

  • tryLock():立即返回,true表示获取成功,false表示锁不可用。
  • tryLock(long time, TimeUnit unit):如当前线程没有被中断则获取锁,成功则返回true,否则当前线程将被休眠,直到发生下列事件:
  •  1. 锁被当前线程获取。
  •  2. 该线程被其它线程中断。
  •  3. 指定的等待时间已过。

如demo-2演示了如何通过可轮询的方式,在指定时间进行一个操作:

 public boolean transferMoney(Account from, Account to, BigDecimal amount, long timeout, TimeUnit timeUnit) throws InterruptedException {
        long stopTime = System.nanoTime() + timeUnit.toNanos(timeout);
        while (true) {
            if (from.lock.tryLock()) {
                try {
                    if (to.lock.tryLock()) {
                        try {
                            from.setBalance(from.getBalance().subtract(amount));
                            to.setBalance(to.getBalance().add(amount));
                            return true;
                        } finally {
                            to.lock.unlock();
                        }
                    }
                } finally {
                    from.lock.unlock();
                }
            }
            if (System.nanoTime() > stopTime) {
                return false;
            }
            Thread.sleep(100);
        }
    }

上述代码通过传入一个等待时间,用于限定轮询获取锁的超时时间,如果超过这个时间还未获取两个锁,则代码直接失败返回false。

1.2 可中断的锁获取操作

可中断的锁可以在取消活动中使用。
一个可中断的规范形式如demo-3:

public void sendMessage(String message) throws InterruptedException {
        lock.lockInterruptibly();
        try {
            doSendMessage(message);
        } finally {
            lock.unlock();
        }
    }

当等待获取锁过程,被中断,方法将抛出中断异常,交给调用方处理。

2 对性能的考量

ReentrantLock在Java5.0提供的竞争性要优于内部锁,但是Java6.0对内部锁进行了改善,使得两者性能差距非常小了。

性能是一个不断变化的目标。

3 公平性

ReentrantLock提供了两种公平性选择。默认是非公平性锁:
在这里插入图片描述
两者区别在于非公平锁允许在首次获取时进行抢占,失败后仍然进行排队:
在这里插入图片描述

4 在内部锁synchronized和ReentrantLock直接进行选择

如果内部锁满足需求,不使用ReentrantLock,只有当你需要如下高级性能时使用:

  • 可定时、可轮询和可中断的操作。
  • 公平队列(内部锁非公平),或者非块结构的锁。

未来的性能改进倾向于synchronized,因为它是基于JVM的,能够进行优化。

5 读写锁

读写锁:一个资源可以被多个读者访问,但是只能被一个写者访问。

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();
}

只暴露了读锁和写锁。

如下等待demo-4是使用读写锁封装Map的示例:

/**
 * 读写锁包装Map
 * @param <K> 键
 * @param <V> 值
 */
public class ReadWriteMap<K, V> {
    private final Map<K, V> map;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Lock w = readWriteLock.writeLock();
    private final Lock r = readWriteLock.readLock();

    public ReadWriteMap(Map<K, V> map) {
        this.map = map;
    }

    public V put(K k, V v) {
        //写锁控制写入-排它
        w.lock();
        try {
            return map.put(k, v);
        } finally {
            w.unlock();
        }
    }

    public V get(K k) {
        //读锁控制读取-共享
        r.lock();
        try {
            return map.get(k);
        } finally {
            r.unlock();
        }
    }
}

读写操作前获取对应读写锁即可。

总结

显式锁提供了内部锁的不具备的一些高级特性,但是不能完全替代内部锁。只有当你需要内部锁没有提供的特性时才使用显式锁。

参考文献

[1]. 《JAVA并发编程实战》.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LamaxiyaFc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值