lock锁

Lock使用方式

  使用Lock需要显式的加锁和解锁

  • 解锁操作需要放在finally块里,防止锁被超界获取
  • 另外,获取锁的操作不能放在try块里,因为Lock是可重入锁,如果外层也已经调用lock()方法,而里层因为调用lock()抛出异常然后调用unlock,外层无法知晓,导致外层代码无法正确同步。
  • Lock lock = ...;
    lock.lock();
    try {
      // access the resource protected by this lock
    } finally {
      lock.unlock();
  • Lock与Synchronize的性能对比

  在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。Brian Goetz对这两种锁在JDK1.5、单核处理器及双Xeon处理器环境下做了一组吞吐量对比的实验,发现多线程环境下,synchronized的吞吐量下降的非常严重,而ReentrankLock则能基本保持在同一个比较稳定的水平上。但与其说ReetrantLock性能好,倒不如说synchronized还有非常大的优化余地,于是到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

Lock实现原理概述

  Lock接口的实现基本都是通过聚合一个同步器的子类来完成线程访问控制的。当调用Lock接口方法时,Lock的实现会将实现代理给同步器的子类,如下代码示例。


import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

class Mutex implements Lock, java.io.Serializable {

    // 实现同步器的子类
    private static class Sync extends AbstractQueuedSynchronizer {
        // 报告是否处于锁定状态
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // 如果state为0,则获取锁
        public boolean tryAcquire(int acquires) {
            assert acquires == 1; // Otherwise unused
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // 通过将state设置为0来释放锁
        protected boolean tryRelease(int releases) {
            assert releases == 1; // Otherwise unused
            if (getState() == 0)
                throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        // 提供Condition实例
        Condition newCondition() {
            return new ConditionObject();
        }

        // 反序列化
        private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // 复位到解锁状态
        }
    }

    // sync实现所有的工作,只需要将实现代理给它
    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    public void unlock() {
        sync.release(1);
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

AbstractQueuedSynchronizer同步器

实现目标

  分别在共享、独占两种模式下,实现阻塞、非阻塞、可中断、可超时的同步状态争夺

实现思路

一个锁的实现中涉及到两个实体:锁和线程
1. 维护锁的状态(维护同步状态:决定线程是否能获得锁、获取锁和释放锁)
2. 维护争夺锁的所有线程的状态(保存当前成功获取锁的线程,挂起和唤醒获取锁失败的线程)

实现概述

  AbstractQueuedSynchronizer使用FIFO队列维护争夺锁的所有线程的状态,使用int类型保存同步状态。基于这两者,此类支持独占和共享两种模式,并定义相应的钩子方法(维护int同步状态变量,根据当前同步状态最终决定在两种模式下是否能成功获得锁,解决了实现思路中的第一点)供子类实现,子类可以根据需要实现相应模式的方法,ReadWriteLock类同时支持这两种模式。
  AbstractQueuedSynchronizer使用模板模式,子类必须定义它的protected方法(上述的钩子方法)以检查或改变同步状态(通过调用方法getState(),setState()和compareAndSetState()),这些方法实现了共享和独占两种模式下非阻塞获取、释放锁的方法,基于这些钩子方法,这个类中的其他方法执行所有的排队和阻塞机制(解决了实现思路的第二点)
  子类通常实现为非public内部类。

方法

  protected方法需要在子类中实现,这些方法提供了同步式和共享式的非阻塞获取、释放同步状态的方法,以及查看当前同步器是否被线程占用的方法。
  这些protected方法默认抛出UnsupportedOperationException异常,这些方法的实现必须是线程安全的,并且必须是非阻塞的,仅支持独占或仅共享模式的子类不需要定义支持未使用模式的方法,ReadWriteLock类同时实现了两种模式。 

posted @ 2018-10-11 09:24  我为了部落  阅读(198)  评论(0)    收藏  举报