Java多线程与并发编程--ReentrantLock

Java多线程与并发编程–ReentrantLock

1.1 底层原理

public class ReentrantLock implements Lock, Serializable

ReentrantLock底层使用了 CAS + AQS队列

1.1.1 CAS

  • 比较并交换,是一个原子操作【CPU指令】。当且仅当 当前值与预期值相等时,更新为新值。
  • 是乐观锁的实现机制
  • 在Java中,通过调用本地方法的方式实现CAS
  • CAS是整个JUC包的底层支持

1.1.2 CAS的缺点

  • ABA问题【解决方案:使用版本号】
  • 循环判断时间长的话,开销会变大
  • 只能保证一个共享变量的原子操作

1.1.3 AQS

  • AbstractQueuedSynchronizer
  • 构建锁和同步器的框架
  • ReentrantLock, Semaphore, ReentrantReadWriteLock, SynchronousQueue, Future等都是基于AQS构建的同步器

1.1.4 AQS原理

  • 若被请求的共享资源空闲,则将当前线程设置为工作线程,并将共享资源设置为锁定状态。

  • 若请求的共享资源被占用,则将当前线程加入到CLH队列并等待

  • AQS使用一个int成员变量来表示同步状态,对该状态的修改使用CAS

  • private volatile int state;//共享变量,使用volatile修饰保证线程可见性
    

1.1.5 AQS对资源的共享方式

  • 独占方式:只有一个线程能执行,如ReentrantLock
  • 共享方式:多个线程可以同时执行,如Semaphore/CountDownLatch

1.1.6 AQS底层使用模板方法模式

  • 同步器的设计基于模板方法模式,自定义同步器的一般步骤:
    • 使用者继承 AbstractQueuedSynchronizer 并重写指定的方法。(这些重写方法很简单,无非是对于共享资源 state 的获取和释放)
    • 将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。

1.2 ReentrantLock的执行流程

  • 线程先通过CAS尝试获取锁
    • 若当前锁已经被占用,则该线程加入AQS队列进行等待
    • 当前锁被释放,在AQS队列等待的线程的争抢策略如下:
      • 公平锁:按照队列的顺序,先到先得
      • 非公平锁:在队列中的线程要获取锁,先通过两次CAS操作抢锁,若抢不到,则线程再次进入队列等待唤醒

ReentrantLock的两个构造函数

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

public ReentrantLock(boolean fair) {
 sync = fair ? new FairSync() : new NonfairSync(); //根据参数创建
}

1.3 lock() 和 unlock()的实现

1.3.1 lock()函数

如果成功通过CAS修改了state,指定当前线程为该锁的独占线程,标志自己成功获取锁。

如果CAS失败的话,调用acquire();

final void lock() { //非公平锁
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

final void lock() { //公平锁
    acquire(1);
}

acquire()函数:

首先,调用tryAcquire(),会尝试再次通过CAS修改state为1,

如果失败而且发现锁是被当前线程占用的,就执行重入(state++);

如果锁是被其他线程占有,那么当前线程执行tryAcquire返回失败,并且执行addWaiter()进入等待队列,并挂起自己interrupt()。

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire() 函数

检查state字段,

若为0,表示锁未被占用,那么尝试占用;

若不为0,检查当前锁是否被自己占用,若被自己占用,则更新state字段,表示重入锁的次数

如果以上两点都没有成功,则获取锁失败,返回false。

protected final boolean tryAcquire(int acquires) { //注意:这是公平的tryAcquire()
        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 tryAcquire(int acquires) { //注意:这是非公平的tryAcquire()
    //获取当前线程
    final Thread current = Thread.currentThread();
    //获取state变量值
    int c = getState();
    if (c == 0) { //没有线程占用锁
        //没有 !hasQueuedPredecessors() 判断,不考虑自己是不是在队头,直接申请锁
        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");
        // 更新state值为新的重入次数
        setState(nextc);
        return true;
    }
    //获取锁失败
    return false;
}

addWaiter() 函数

当前线程加入AQS双向链表队列。

写入之前需要将当前线程包装为一个 Node 对象(addWaiter(Node.EXCLUSIVE))。

首先判断队列是否为空,不为空时则将封装好的 Node 利用 CAS 写入队尾,如果出现并发写入失败就需要调用 enq(node); 来写入了。

/**
 * 将新节点和当前线程关联并且入队列
 * @param mode 独占/共享
 * @return 新节点
 */
private Node addWaiter(Node mode) {
    //初始化节点,设置关联线程和模式(独占 or 共享)
    Node node = new Node(Thread.currentThread(), mode);
    // 获取尾节点引用
    Node pred = tail;
    // 尾节点不为空,说明队列已经初始化过
    if (pred != null) {
        node.prev = pred;
        // 设置新节点为尾节点
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 尾节点为空,说明队列还未初始化,需要初始化head节点并入队新节点
    enq(node);
    return node;
}

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

enq()的处理逻辑就相当于自旋加上CAS保证一定能写入队列。

acquireQueued() 函数

写入队列后,需要挂起当前线程

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

首先会根据 node.predecessor() 获取到上一个节点是否为头节点,如果是则尝试获取一次锁,获取成功就万事大吉了。

如果不是头节点,或者获取锁失败,则会根据上一个节点的 waitStatus 状态来处理(shouldParkAfterFailedAcquire(p, node))。

waitStatus 用于记录当前节点的状态,如节点取消、节点等待等。

shouldParkAfterFailedAcquire(p, node) 返回当前线程是否需要挂起,如果需要则调用 parkAndCheckInterrupt():

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

他是利用 LockSupportpart 方法来挂起当前线程的,直到被唤醒。

1.3.2 unlock() 函数

释放时候,state–,通过state==0判断锁是否完全被释放。

成功释放锁的话,唤起一个被挂起的线程

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
        	//唤醒被挂起的线程
            unparkSuccessor(h);
        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;
}

1.4 JUC包底层支撑结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpYQdSL7-1626759977369)(resource/1626757722454.png)]

1.5 ReentrantLock的基本使用

1.5.1 简单使用

public class Test1 {
    private static final Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        new Thread(() -> test(), "线程A").start();
        new Thread(() -> test(), "线程B").start();
    }

    public static void test(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "获得锁");
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e){
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "释放锁");
            lock.unlock();
        }
    }
}
线程A获得锁
线程A释放锁
线程B获得锁
线程B释放锁

1.5.2 公平锁实现

public class Test2 {
    private static final Lock lock = new ReentrantLock(true);
    public static void main(String[] args) {
        new Thread(() -> test(), "线程A").start();
        new Thread(() -> test(), "线程B").start();
        new Thread(() -> test(), "线程C").start();
        new Thread(() -> test(), "线程D").start();
        new Thread(() -> test(), "线程E").start();
    }

    public static void test(){
        for (int i = 0; i < 2; i++) {
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName() + "获得锁");
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e){
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "释放锁");
                lock.unlock();
            }
        }
    }
}

线程A获得锁
线程A释放锁
线程B获得锁
线程B释放锁
线程C获得锁
线程C释放锁
线程D获得锁
线程D释放锁
线程E获得锁
线程E释放锁
线程A获得锁
线程A释放锁
线程B获得锁
线程B释放锁
线程C获得锁
线程C释放锁
线程D获得锁
线程D释放锁
线程E获得锁
线程E释放锁

1.5.3 非公平锁实现

public class Test2 {
    private static final Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        new Thread(() -> test(), "线程A").start();
        new Thread(() -> test(), "线程B").start();
        new Thread(() -> test(), "线程C").start();
        new Thread(() -> test(), "线程D").start();
        new Thread(() -> test(), "线程E").start();
    }

    public static void test(){
        for (int i = 0; i < 2; i++) {
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName() + "获得锁");
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e){
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "释放锁");
                lock.unlock();
            }
        }
    }
}
线程A获得锁
线程A释放锁
线程A获得锁
线程A释放锁
线程B获得锁
线程B释放锁
线程B获得锁
线程B释放锁
线程C获得锁
线程C释放锁
线程C获得锁
线程C释放锁
线程D获得锁
线程D释放锁
线程D获得锁
线程D释放锁
线程E获得锁
线程E释放锁
线程E获得锁
线程E释放锁

1.5.4 响应中断

public class Test3 {
    private static final Lock lock1 = new ReentrantLock();
    private static final Lock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread a = new Thread(() -> test(), "线程A");
        Thread b = new Thread(() -> test(), "线程B");
        a.start();
        b.start();
        a.interrupt();
    }

    public static void test() {
        try {
            lock1.lockInterruptibly();
            TimeUnit.SECONDS.sleep(50);
            lock2.lockInterruptibly();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock1.unlock();
            lock2.unlock();
            System.out.println(Thread.currentThread().getName() + " 正常结束");
        }
    }
}
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at thread.reentrantlock.Test3.test(Test3.java:21)
	at thread.reentrantlock.Test3.lambda$main$0(Test3.java:12)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "线程A" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
	at thread.reentrantlock.Test3.test(Test3.java:27)
	at thread.reentrantlock.Test3.lambda$main$0(Test3.java:12)
	at java.lang.Thread.run(Thread.java:748)
线程B 正常结束
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值