Skip to the content.

JAVA锁-ReenrantLock

背景

Synchronized 是一种内置锁,容易使用,但是不容易操作。

ReenrantLock 其实也是一种锁, 相比较Synchronized来说,这个锁更加灵活,更容易操作。

为了保障临界资源的数据安全性,就是通过锁来进行保障的。

ReentrantLock的使用

public class AccountingSyncLock implements Runnable {
    /*
        共享资源(临界资源)
    */
    static int i=0;

    /*
    	该lock属于类,锁定的对象是类实例
    */
    private ReentrantLock lock = new ReentrantLock();

    public void increase(){
        try {
            // 获取锁,如果获取到了,就可以操作临界资源,i++。
            lock.lock();
            i++;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            //必须在finally中进行解锁操作,如果不在 finally解锁,有可能代码出现异常锁没被释放,
            lock.unlock();
        }
    }

    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AccountingSync instance=new AccountingSync();
        // 两个线程操作同一个实例
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();
        t2.start();
        // 等待t1线程执行完
        t1.join();
        // 等待t2线程执行完
        t2.join();

        // 主线线程输出
        System.out.println(i);
    }
}

公平锁和非公平锁

里面其实是有一个队列的,维护多个线程获取同个资源。

公平锁就是将新的线程插入到队列尾部。

非公平锁是将新的线程通过CAS插入头部,如果插入头部失败的话,就插入队列尾部就和公平锁一致了。

一: 内部(AbstractQueuedSynchronizer)维护了一个队列,维护多个线程获取同个资源。
二: reentrantlock 默认是非公平锁。
public ReentrantLock() {
    sync = new NonfairSync();
}

公平锁

    /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        /**
         * lock 设置锁
         * lock()内部调用acquire(1),为何是”1”呢?
         * 首先我们知道ReentrantLock是独占锁,1表示的是锁的状态state。
         * 对于独占锁而言,如果所处于可获取状态,其状态为0,当锁初次被线程获取时状态变成1。
         */
        final void lock() {
            acquire(1);
        }

        ...
    }

非公平锁

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

        /**
         * lock()通过compareAndSetState尝试设置锁状态
         * 若成功直接将锁的拥有者设置为当前线程,否则调用acquire()尝试获取锁
         *
         * 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);
        }

        ....
    }

compareAndSetState(0, 1) 是以原子的方式操作当前线程;若当前线程的状态为expect,则设置它的状态为update。

protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

setExclusiveOwnerThread:设置当前线程为锁的拥有者

protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

AbstractQueuedSynchronizer 队列

  Exclusive (独占) Share (共享) 结果
tryAcquire 获取资源   成功true,失败false
tryRelease 释放资源   成功true,失败false
tryAcquireShared   获取资源 负数表示失败; 0表示成功,但没有剩余可用资源; 正数表示成功,且有剩余资源。
tryReleaseShared   释放资源 如果释放后允许唤醒后续等待结点,返回true,否则返回false。

state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。

此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。

当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state最终为0。

AQS 就是 AbstractQueuedSynchronizer,AQS是JUC中很多同步组件的构建基础。

简单来讲,它内部实现主要是状态变量state和一个FIFO队列来完成。

同步队列的头结点是当前获取到同步状态的结点,获取同步状态state失败的线程,会被构造成一个结点(或共享式或独占式)加入到同步队列尾部(采用自旋CAS来保证此操作的线程安全),随后线程会阻塞;释放时唤醒头结点的后继结点,使其加入对同步状态的争夺中。