Java并发之ReentrantLock锁

简介

一种可重入的互斥锁,经由Java5引入,支持一个线程对资源的重复加锁。它和synchronized语句和方法访问的隐式监视器锁,有相同的基本行为和语义,但是功能更强大。之所以存在synchronized这种内置锁功能,还要新增Lock接口,是因为手动进行锁获取、释放,获取锁A、B,释放A、B,再去获取C、D…使用Lock会容易一些。

一、ReentrantLock定义

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;
    
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        
        abstract void lock();

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
//这里不要
        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

    /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

    //默认非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    //fair传入true,采取公平锁策略
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    public void lock() {
        sync.lock();
    }

   //这里省略了一部分...
    public void unlock() {
        sync.release(1);
    }

    
    public Condition newCondition() {
        return sync.newCondition();
    }
    //省略...
}

这里截取了部分ReentrantLock源码,我们可以看到这里采用了组合模式把lock和unlock委托给同步器完成

二、ReentrantLock的使用

显式调用,获取同步锁、释放同步锁即可

ReentrantLock lock = new ReentrantLock();//默认参数false,不公平锁
Condition condition = lock.newCondition();
        lock.lock();//若被其他资源锁定,会在此等待锁释放,达到暂停效果
        try {
        	while(条件判断表达式){
        		condition.wait();
        	}
            //do something
        }finally {
            lock.unlock();
        }
//这么写和上面一个效果
public synchronized void method(){}

三、Lock接口

锁对象,在java中锁是控制多个线程访问共享资源的方式,一个锁能防止多个线程同时访问共享资源(但有的锁就允许多个线程并发访问,如读写锁)。在Lock接口出现之前,是靠synchronized关键字来实现锁的。但是Java5中的并发包里新增了Lock接口实现锁的功能,提供了与synchronized关键字类似的同步功能,只是在使用时需要显示的获取、释放锁。虽然缺少synchronized那样隐式释放锁的便捷,但是却拥有了锁获取、释放的可操作性,并中断的获取锁以及超时获取锁等多种synchronized不具备的同步特性。
Lock接口主要方法:

void lock();//执行此方法时,若锁处于空闲状态,当前线程将获取到锁。否则,就禁用当前线程知道当前线程获取到锁
boolean tryLock();//如果锁可用,就获取并返回true。否则就返回false,但是当前线程不会被禁用,当前线程还可以继续执行代码。
void unLock();//当前线程将释放持有的锁,且锁只能由持有者释放。若某线程未持有锁,就执行该方法,会有异常
Condition newCondition();//条件对象,获取等待通知的组件。与当前锁绑定,当前线程只有获取了锁,才能调用该组件的await()方法

四、解决线程同步的实例

public class Ticket implements Runnable {
    private int num = 100;//假设一共有100张票
    ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            lock.lock();
            if (num>0){
                System.out.println(Thread.currentThread().getName()+"卖了"+num--);
            }
            lock.unlock();
        }
    }
}

五、重入锁

当一个线程得到一个对象时,再次请求该对象锁时是可以再次得到该对象的锁。Java里面的内置锁(synchronized)和Lock(ReentrantLock)都是可重入的。

在公平锁和非公平锁中,都对重入锁进行了实现。

if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }

synchronized的重入锁特性:

public class SynchronizedTest {
    public void method1(){
        synchronized (SynchronizedTest.class){
            System.out.println("方法1获取到的对象锁运行了");
            method2();
        }
    }

    public void method2() {
        synchronized (SynchronizedTest.class){
            System.out.println("方法1调用的方法2重入锁,也运行了");
        }
    }
    public static void main(String []args){
        new SynchronizedTest().method1();
    }
}

ReentrantLock的重入锁特性:

public class ReentrantLockTest {
    private Lock lock = new ReentrantLock();
    public void method1(){
        lock.lock();
        try {
            System.out.println("方法1获取到了ReentrantLock锁运行了");
            method2();
        }finally {
            lock.unlock();
        }
    }
    public void method2(){
        lock.lock();
        try {
            System.out.println("方法1调用方法2冲入ReentrantLock业运行了");
        }finally {
            lock.unlock();
        }
    }
    public static void main(String []args){
        new ReentrantLockTest().method1();
    }
}

六、非公平锁的实现

在非公平锁中,每当线程执行lock方法时,都尝试利用CAS把state从0设置成1。那Doug lea又是怎么实现锁的非公平性的呢?假设:
(1)持有锁的线程A正在running,队列中的BCDEF被挂起等待被唤醒;
(2)在某一刻,线程A执行unlock,唤醒线程B;
(3)同时线程G执行lock,会怎么样呢?B、G拥有相同的获取锁的优先级,同时执行CAS指令竞争锁。如果G成功了,B就得被挂起等待重新唤醒;
即使B等了很久,在和G竞争锁的斗争中失败了,世道如此不公!

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

这里来描述一下非公平锁的竞争过程:
(1)线程A、B同时执行CAS指令,假设A成功,B失败,线程A成功获取到锁,并把同步器中的exclusiveOwnerThread设置为线程A
(2)失败的B,在nofairTryAcquire方法中,会尝试再次获取锁。Doug lea会尝试在多处重新获取锁,应该是在这段时间内,如果线程A释放锁,线程B就可以不用挂起而直接获取到锁。
在这里插入图片描述

七、公平锁的实现

在公平锁中,每当线程执行lock方法,如果同步器的队列中有线程再等待,就直接加入到队列中。假设:
(1)持有锁的线程A正在running,队列中的BCDEF挂起等待被唤醒;
(2)线程G执行lock方法,由于队列中有BCDEF在等待,所以G直接加入到队列中;
每个线程获取到锁的过程是公平的,等待时间最长的线程会最先被唤醒而获取锁。

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

八、条件变量Condition

Condition在很大程度上是为了解决Object.wait/notify/notifyAll难以使用的问题。

public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;
		
		//省略...
		public final void signal(){}
		public final void signalAll(){}
		public final void awaitUninterruptibly(){}
		public final void await() throws InterruptedException{}
}

1.在synchronized中,所有线程都在同一个Object的条件队列上等待。而ReentrantLock中每个Condition都维护一个条件队列。
2.每一个Lock可以有任意数据的Condition对象,Condition是和Lock绑定的,所有就有Lock的公平特性:如果是公平锁,线程为按照FIFO的顺序从Condition.await中释放;如果是非公平锁,那后续的锁竞争就不保证FIFO的顺序了。
3.Condition接口的定义方法,await对应Object.wait,signal对应Object.notify,signalAll对应Object.notifyAll。Condition的接口改变名称,就是为了避免和Object中的wait/notify/notifyAll在语义和使用上混淆

九、总结

1.ReentrantLock提供了内置锁类似的功能和内存语义
2.ReentrantLock还提供了定时的锁等待、可中断的锁等待、公平性以及实现非块结构的加锁、Condition。对线程的等待、唤醒更加灵活,一个ReentrantLock可以有多个Condition实例,故更有扩展性。ReentrantLock需要显式的获取锁,并在finally中释放。
3.ReentrantLock在性能上优于synchronized,那为何不放弃内置锁?之前提过了,这里不再赘述
4.在JDK5中,内置锁比ReentrantLock多一个优点:在线程转储中能给出在哪些调用帧中获得了哪些锁,并能检测、识别发生死锁的线程。ReentrantLock的非块状特性意味着,获取锁的操作不能与特定的栈帧关联起来,然而内置锁却可以。
5.内置锁是JVM的内置属性,未来更可能提升synchronized的性能,例如对线程封闭的锁对象消除优化,增加锁粒度来消除内置锁的同步

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值