ReentrantLock与synchronized的学习笔记

首先是两者区别:
synchronized属于非公平锁,不支持锁共享,功能单一。
ReentrantLock功能比较丰富:公平锁、尝试加锁,带超时时间的获取锁,获取锁支持响应中断等场景都适合使用。

详细解释ReentrantLock功能

公平锁&不公平锁:

其实源码中解释的很简单粗暴:

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

(emmmmmmm.jpg)
实现机制也是大致相同,
公平锁,加锁前会先看下前面有没有其他线程在排队,如果前面有其他线程排队,就乖乖排在队尾。

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

非公平锁的流程是他不管前面有没有线程在排队,都先尝试获取一下锁,获取成功就直接占用。

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

关键在于hasQueuedPredecessors这方法,用于查看当前队列是否有其他排队

尝试加锁

synchronized就像专一的男人,一旦发起加锁流程,就一心等待,即使这时女孩有男朋友(其他线程抢占),他还是会一直等待。
ReentrantLock不一样,他有个技能,叫 tryLock,尝试获取女孩子的心,发现一旦有其他竞争对象,他立马折返撤退。而且这个tryLock 还有个特殊技能,获取锁支持带超时时间,如果女孩在指定时间内没给机会,就会返回。(海王本王石锤了)
代码中trylock调用的是sync中的nonfairTryAcquire方法,而不是在nonfairSync中(就像代表正义的反派角色,或者反过来 )

/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair try for trylock method.
 */
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;
}

带超时时间获取锁

先设置超时时间,单位nano(System.nanoTime)然后调用tryAcquireNanos——doAcquireNanos方法

(注释太长)
public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
###########################################################
(注释太长)
    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
###########################################################

    /**
     * Acquires in exclusive timed mode.
     *
     * @param arg the acquire argument
     * @param nanosTimeout max wait time
     * @return {@code true} if acquired
     */
    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

在方法中可以看到

nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;

与获取锁流程类似,只是加了一个超时时间的判断,先用当前时间加超时间隔时间得到一个到期时间,然后在获取锁的for 循环体中判断是否超时。
大家注意到调用shouldParkAfterFailedAcquire 方法判断是否需要park的时候,如果返回true 会再判断nanosTimeout > spinForTimeoutThreshold。
spinForTimeoutThreshold 这个参数是什么意思?
这个值是自旋的最小阈值,这里被Doug Lea 设置成了1000,表示1000纳秒,也就是说如果剩余的时间不足1000纳秒,则不需要park。

static final long spinForTimeoutThreshold = 1000L;

为什么不需要park,因为离到期时间太短,阻塞之后再唤醒的时间可能都不够(这里是<1000纳秒),代价还很高。

所以最好的方式是下一次for 循环要么获取到锁,要么滚蛋,不浪费一次pack 再 unpack的代价。

响应中断

/**
 * Acquires in exclusive uninterruptible mode for thread already in
 * queue. Used by condition wait methods as well as acquire.
 *
 * @param node the node
 * @param arg the acquire argument
 * @return {@code true} if interrupted while waiting
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

这里是如果应该park,并且发生了中断,设置中断标识信息(interrupted字段),获取到锁之后返回这个中断标识信息。

    /**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

parkAndCheckInterrupt这个方法会让线程park(阻塞),同时会调用 Thread.interrupted() 返回线程是否发生过中断,这个方法会清除线程的中断标识位,是否发生过中断的状态保持在 interrupted 里面,后面返回了。
再回来

在这里插入图片描述
只要发生了中断,直接抛 InterruptedException 异常,获取锁流程不再继续。

  • 所以说ReentrantLock 如果使用 lockInterruptibly,如果发生了中断是会立即停止获取锁的流程,是响应中断的,

  • 但是如果使用 lock 是不响应中断,实际上是把中断吃掉了,延迟中断(中断标识返回后主动中断)。

得知:synchronized 获取锁的过程中是不能被中断,ReentrantLock 支持。

总结

底层实现:synchronized 是 JVM层面的锁,是Java关键字,通过monitor对象来完成(monitorenter与monitorexit),ReentrantLock 是从jdk1.5以来(java.util.concurrent.locks.Lock)提供的API层面的锁。

锁的对象:synchronzied 锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock 是根据volatile 变量 state 标识锁的获得/争抢。

实现机制上:synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向内核态申请重量级锁,ReentrantLock实现则是通过利用CAS(CompareAndSwap)自旋机制保证线程操作的原子性和volatile 保证数据可见性以实现锁的功能。

释放锁方式上:synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动释放锁;ReentrantLock 需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。一般通过lock() 和unlock() 方法配合 try/finally 语句块来完成。

带条件的锁:synchronized不能绑定条件;ReentrantLock 可以绑定Condition 结合await()/singal() 方法实现线程的精准唤醒,而不是像synchronized通过Object类的wait()/notify()/notifyAll() 方法要么随机唤醒一个线程要么唤醒全部线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值