六、ReentrantLock

一、基础概念

  • 基于AQS+CAS+volatile实现的。(什么是AQS往下看完ReentrantLock源码就知道了)
    • ReentrantLock有一个内部类Sync继承了AQS(AbstractQueuedSynchronizer)基类,ReentrantLock并没有直接去继承AQS。而是通过sync来构建锁的。
    • AQS相当于一个“锁框架”,不同的锁实现不一样。用state、线程链表来构建的
    • state是个什么鬼?
      • state是AQS中的一个由volatile修饰的int类型变量,多个线程会通过CAS的方式修改state,在并发情况下,只会有一个线程成功的修改state(从0~1)
      • 如果线程修改state失败怎么办?
        • 如果线程没有拿到锁资源,会到AQS的双向链表中排队等待(在期间,线程可能会挂起)
    • AQS的双向链表(队列)是个啥?
      • AQS中的双向链表是基于内部类Node在维护,Node中包含prev,next,thread属性,并且在AQS中还有两个属性,分别是head,tail。
  • 与Synchronized的区别
    • 实现不一样,ReentrantLock底层是AQS,Synchronized底层是ObjectMonitor
    • 用法不一样,ReentrantLock更加多样化。
    • Synchronized是非公平,可重入,互斥锁。ReentrantLock是非公平/公平,可重入,互斥锁
  • 如果是竞争很激烈的话,建议用ReentrantLock,因为不牵扯用户态和内核态的交互。

二、使用

lock

public class Test {
    private static int count = 0;
    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        new ThreadDemo().start();
        new ThreadDemo().start();
        Thread.sleep(1000);
        System.out.println(count);
    }
    static class ThreadDemo extends Thread {
        @Override
        public void run() {
            reentrantLock.lock();
            try {
                for (int i = 0; i < 1000; i ++) {
                    count++;
                }
            } finally {
                reentrantLock.unlock();
            }
        }
    }
}

trylock

public class Test {
    private static int count = 0;
    private static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        new ThreadDemo().start();
        new ThreadDemo().start();
        Thread.sleep(4000);
        System.out.println(count);
    }

    static class ThreadDemo extends Thread {
        @Override
        public void run() {
            try {
                if (reentrantLock.tryLock(1, TimeUnit.SECONDS)) {
                    try {
                        for (int i = 0; i < 1000; i++) {
                            count++;
                        }
                        Thread.sleep(2000);
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

lockInterruptibly

public class Test {
    private static int count = 0;
    private static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            reentrantLock.lock();
            try {
                for (int i = 0; i < 1000; i++) {
                    count++;
                }
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                reentrantLock.unlock();
            }
        });
        t1.start();
        Thread.sleep(500);
        Thread t2 = new Thread(() -> {
            try {
                reentrantLock.lockInterruptibly();
                for (int i = 0; i < 1000; i++) {
                    count++;
                }
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " : 被打断了, 我可以干点别的");
            }
        });
        t2.setName("t2");
        t2.start();
        Thread.sleep(1000);
        t2.interrupt();
        System.out.println(count);
    }
}

condition

比肩于synchronized的wait、notifiy、notifyAll。可以设置多个条件的等待唤醒。synchronized只能是一种。

public class Test {
    private static final ReentrantLock lock = new ReentrantLock();
    private static final Condition c1 = lock.newCondition();
    private static final Condition c2 = lock.newCondition();
    private static final List<Integer> list = new ArrayList<>();

    private static void consumer() {
        lock.lock();
        try {
            while (list.size() == 0) {
                c2.await();
            }
            list.remove(0);
            System.out.println("消费了一个商品");
            c1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private static void producer(Integer i) {
        lock.lock();
        try {
            while (list.size() >= 20) {
                c1.await();
            }
            list.add(i);
            System.out.println("生产者物品数量" + list.size());
            c2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                Test.producer(i);
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                Test.consumer();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }
}

三、源码(jdk1.8)

lock()

在这里插入图片描述

公平锁

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

非公平锁

final void lock() {
    // 非公平锁加锁的时候,第一件事就是先用CAS抢一次锁。
    if (compareAndSetState(0, 1))
        // 如果抢到了,就将当前互斥锁的线程设置成自己。
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

acquire()

AQS方法,公平锁、非公平锁通用

public final void acquire(int arg) {
    //1. 调用tryAcquire方法:尝试获取锁资源(非公平、公平),拿到锁资源,返回true,直接结束方法。 没有拿到锁资源,
    //   需要执行&&后面的方法

    //2. 当没有获取锁资源后,会先调用addWaiter:会将没有获取到锁资源的线程封装为Node对象,
    //   并且插入到AQS的队列的末尾,并且作为tail

    //3. 继续调用acquireQueued方法,查看当前排队的Node是否在队列的前面,如果在前面(head的next),尝试获取锁资源
    //   如果没在前面,尝试将线程挂起,阻塞起来!
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire()

  • tryAcquire分为公平和非公平两种
  • tryAcquire主要做了两件事:
    • 如果state为0,尝试获取锁资源
    • 如果state不为0,看一下是不是锁重入操作

公平锁

protected final boolean tryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取AQS的锁状态
    int c = getState();
    // 当state == 0就说明当前没有线程拿到锁,当前线程就可以去抢锁了
    if (c == 0) {
         // 判断是否有线程在排队,如果有线程排队,返回true,配上前面的!,那会直接不执行返回最外层的false
         // 如果没有线程排队,直接CAS尝试获取锁资源
        if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
            // 将当前占用这个互斥锁的线程属性设置为当前线程
            setExclusiveOwnerThread(current);
            // 返回true,拿锁成功
            return true;
        }
    }
    // 当前state != 0,说明有线程占用着锁资源
    // 判断拿着锁的线程是不是当前线程(锁重入)
    else if (current == getExclusiveOwnerThread()) {
        // 将state再次+1
        int nextc = c + acquires;
        // 锁重入是否超过最大限制
        // 01111111 11111111 11111111 11111111   + 1
        // 10000000 00000000 00000000 00000000
        // 抛出error
        if (nextc < 0)
            // 重入次数最大值就是int的最大值了
            throw new Error("Maximum lock count exceeded");
        // 将值设置给state
        setState(nextc);
        // 返回true,拿锁成功
        return true;
    }
    return false;
}

非公平锁

final boolean nonfairTryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取AQS的锁状态
    int c = getState();
    // 当state == 0就说明当前没有线程拿到锁,当前线程就可以去抢锁了
    if (c == 0) {
        // 没人占用锁资源,我直接抢一波(不管有没有线程在排队)
        if (compareAndSetState(0, acquires)) {
            // 将当前占用这个互斥锁的线程属性设置为当前线程
            setExclusiveOwnerThread(current);
            // 返回true,拿锁成功
            return true;
        }
    }
    // 当前state != 0,说明有线程占用着锁资源
    // 判断拿着锁的线程是不是当前线程(锁重入)
    else if (current == getExclusiveOwnerThread()) {
        // 将state再次+1
        int nextc = c + acquires;
        // 锁重入是否超过最大限制
        // 01111111 11111111 11111111 11111111   + 1
        // 10000000 00000000 00000000 00000000
        // 抛出error
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 将值设置给state
        setState(nextc);
        // 返回true,拿锁成功
        return true;
    }
    return false;
}

addWaiter()

在获取锁资源失败后,需要将当前线程封装为Node对象,并且插入到AQS队列的末尾

// 将当前线程封装为Node对象,并且插入到AQS队列的末尾
private Node addWaiter(Node mode) {
    // 将当前线程封装为Node对象,mode为null,代表互斥锁
    Node node = new Node(Thread.currentThread(), mode);
    // pred是tail节点
    Node pred = tail;
    // 如果pred不为null,有线程正在排队
    if (pred != null) {
        // 将当前节点的prev,指定tail尾节点
        node.prev = pred;
        // 以CAS的方式,将当前节点变为tail节点
        if (compareAndSetTail(pred, node)) {
            // 之前的tail的next指向当前节点
            pred.next = node;
            return node;
        }
    }
    // 添加的流程为,  自己prev指向、tail指向自己、前节点next指向我
    // 如果上述方式,CAS操作失败,导致加入到AQS末尾失败,如果失败,就基于enq的方式添加到AQS队列
    enq(node);
    return node;
}
// enq,无论怎样都添加进入
private Node enq(final Node node) {
    for (;;) {
        // 拿到tail
        Node t = tail;
        // 如果tail为null,说明当前没有Node在队列中
        if (t == null) {
            // 创建一个新的Node作为head,并且将tail和head指向一个Node
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 有了没用的Node节点当头了,那么当前这个节点就可以插入链表的tial了。
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

acquireQueued()

  • acquireQueued方法会查看当前排队的Node是否是head的next,如果是,尝试获取锁资源,如果不是或者获取锁资源失败那么就尝试将当前Node的线程挂起(unsafe.park())
  • 在挂起线程前,需要确认当前节点的上一个节点的状态必须是小于等于0,
    • 如果为1,代表是取消的节点,不能挂起
    • 如果为-1,代表挂起当前线程
    • 如果为-2,-3,需要将状态改为-1之后,才能挂起当前线程
// acquireQueued方法
// 查看当前排队的Node是否是head的next,
// 如果是,尝试获取锁资源,
// 如果不是或者获取锁资源失败那么就尝试将当前Node的线程挂起(unsafe.park())
final boolean acquireQueued(final Node node, int arg) {
    // 标识。
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 拿到上一个节点
            final Node p = node.predecessor();
            // 说明当前节点是head的next
            //  竞争锁资源,成功:true,失败:false
            if (p == head && tryAcquire(arg)) {
                // 进来说明拿到锁资源成功
                // 将当前节点置位head,thread和prev属性置位null
                setHead(node);
                // 帮助快速GC
                p.next = null; // help GC
                // 设置获取锁资源成功
                failed = false;
                return interrupted;
            }
            /// 如果不是或者获取锁资源失败,尝试将线程挂起
            // 第一个事情,当前节点的上一个节点的状态正常!
            // 第二个事情,挂起线程
            // 通过LockSupport将当前线程挂起
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 拿到前一个节点的状态
    int ws = pred.waitStatus;
    // 如果上一个节点为 -1
    if (ws == Node.SIGNAL)
       // 返回true,挂起线程
        return true;
    // 如果上一个节点是取消状态
    if (ws > 0) {
        // 循环往前找,找到一个状态小于等于0的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 将小于等于0的节点状态该为-1
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

unlock()

  • 释放锁资源:
    • 将state-1。
    • 如果state减为0了,唤醒在队列中排队的Node。(一定唤醒离head最近的)
  • 释放锁不分公平和非公平,就一个方法。
public void unlock() {
    sync.release(1);
}
// 真正释放锁资源的方法
public final boolean release(int arg) {
    // 核心的释放锁资源方法
    if (tryRelease(arg)) {
        // 释放锁资源释放干净了。  (state == 0)
        Node h = head;
        // 如果头节点不为null,并且头节点的状态不为0,唤醒排队的线程
        if (h != null && h.waitStatus != 0)
            // 唤醒线程
            unparkSuccessor(h);
        return true;
    }
    // 释放锁成功,但是state != 0
    return false;
}

tryRelease()

// 核心的释放锁资源方法
protected final boolean tryRelease(int releases) {
    // 获取state - 1
    int c = getState() - releases;
    // 如果释放锁的线程不是占用锁的线程,抛异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 是否成功的将锁资源释放利索 (state == 0)
    boolean free = false;
    if (c == 0) {
        // 锁资源释放干净。
        free = true;
        // 将占用锁资源的属性设置为null
        setExclusiveOwnerThread(null);
    }
    // 将state赋值
    setState(c);
    // 返回true,代表释放干净了
    return free;
}

unparkSuccessor()

// 唤醒节点
private void unparkSuccessor(Node node) {
    // 获取头结点的状态
    int ws = node.waitStatus;
    // 如果头节点状态小于0,那么就设为0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 获取头结点的下一个节点
    Node s = node.next;
    // 如果头结点的下一个节点为null,或者状态是1。那么就从尾节点倒着开始往前找,找到那个状态小等于0且距离head最近节点。
    // 为什么要从尾节点倒着始找?addWaiter添加节点的时候,是先把当前节点的pre指向tail,然后将tail设置为当前节点,最后将尾节点的next设置为当前节点。这就有个问题了,如果只执行了第一步,然后要唤醒,是不是从头开始找,找不到这个要挂起的线程。
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        // 节点唤醒
        LockSupport.unpark(s.thread);
}

四、总结流程(个人总结,可能有错误)

lock 加锁

备注:抢锁的逻辑都是执行一次cas

  • 非公平锁上来先抢一把,公平锁不会抢。(ReentrantLock中的Sync类中的lock方法)
  • 尝试抢锁(AQS中的tryAcquire方法)
    • 公平锁:拿到当前线程以及AQS锁状态
      • 当state==0时,判断队列是不是空,空的话抢锁。非空的话需要去看一下head的next是不是当前线程,如果是也去抢锁。
      • 当state!=0时,判断当前线程,是不是拿锁的线程,如果是话,就重入拿锁啊。
    • 非公平锁:拿到当前线程以及AQS锁状态
      • 当state==0时,直接去抢一次锁。
      • 当state!=0时,判断当前线程,是不是拿锁的线程,如果是话,就重入拿锁啊
  • 抢不到锁了,就添加队列(AQS中的addWaiter方法)
    • 使用当前线程构造一个Node节点。判断尾节点(tail)不是null。那么就将Node节点加入到tail的后面。加入逻辑如下:
      1. 将Node节点的pre设置为tail
      2. 将tail设置为当前节点
      3. 将原来的tail的next设置为当前节点
    • 起一个死循环
      • 如果tail是个null。那么就创建一个Node但是没有线程,这就是那个没有实际意义的Node,将这个Node变为head和tail
      • 如果tail不是null。那么就用上面的逻辑将Node加入到tail后面,这个是CAS操作,直到加入成功,这个循环才break
  • 起一个死循环
    • 判断addWatier创建出来的Node,pre是不是head,如果是就tryAcquire抢锁,抢锁成功,将当前node设置为head,并将当前node的pre的next设置为null,最后循环break。
    • 如果不是判断当前Node的前一个Node状态
      • 是-1,如果是-1就直接挂起当前线程
      • 是1,从当前Node往前找,直到找到一个Node状态是小于等于0的,将当前节点挂在它的后面。流程就是addWaiter中将Node挂在tail的流程
      • 是-2,-3,0,那么cas将这个节点的状态设置为-1。
      • 最后将当前线程park挂起,等待唤醒。

lock 释放锁

  • 调用ReentrantLock中的tryRelease方法,释放锁
    • 判断当前线程是不是持有锁的线程,如果不是,那么直接抛出异常
    • 用当前state - 1 == 0 ? 如果是0,那么就将AQS中的锁线程设置为null。将计算结果赋值给state
  • 如果state==0的话,获取head,不为null且状态不是0,那么就从后往前,找一个距离head最近的且状态是0或者-1的Node。unpark唤醒它。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值