★ 1、讲讲 Lock 锁
是一个接口,有三个实现类,分别是常用的 可重入锁,读锁、写锁
。常用的是可重入锁。
加锁使用lock() 方法,解锁使用 unlock() 方法。Lock的底层是 AQS+CAS机制 实现。
Lock 常用子类 可重入锁ReentrantLock 有两种模式, 公平锁模式、非公平锁模式
。
公平锁模式
和非公平锁模式
的应用
默认一般创建的是非公平锁,就是允许线程插队,而不是按先来后到顺序
并发量高的,非公平可能会导致线程饿死 === 做中间件,比如rocketmq 就需要关注锁公平和不公平
mq 消息队列的应用,比如网易云多个用户的评论->mq->如果是非公平锁,那么导致线程饥饿,导致等待时间过长-不稳定
解决:mq源码的queue包下有:
RoundQueue(线程不安全),ConcurrentTreeMap(线程安全-put 方法使用了lock 加锁,且 lock = new ReentrantLock(true);)
可重入锁的意思是 对于同一线程可以重复去获取锁。应用场景--递归,例如文件夹遍历目录下的所有文件名。
★ 2、和synchronized 的使用区别/ 说说lock 和 synchronized 锁的区别
-
synchronized 是一个
关键字
,使用C++实现的,没办法控制锁的开始、锁结束,也没办法中断线程的执行 -
而 lock 是
java层面的实现
,可以获取锁的状态,开启锁,释放锁,通过设置可以中断线程的执行,更加灵活 -
是否自动是否锁:synchronized 会自动是否锁,而 lock 需要手动调用unlock 方法释放,否则会死循环
lock.lock();//其他没有拿到锁的线程?阻塞 卡着不动 boolean res = lock.tryLock(1000, TimeUnit.MILLISECONDS);//一秒之后如果没有拿到锁,就返回false lock.lockInterruptibly();//中断方法
★ 3、讲讲 trylock、lock方法
lock 锁设计上的核心成员:锁状态、锁拥有者、等待队列
源码方面:在 ReentrantLock 中 使用了关键成员是同步器AQS(源码中的Sync)
trylock方法:获取锁/是否锁成功
-
锁的状态,0 代表未占用锁,大于0 则代表占用锁的次数。
-
首先当前线程以CAS的方式,尝试将锁的状态从0修改成1,就是尝试获取锁。
-
获取到了就把当前线程设置给AQS的属性exclusiveOwnerThread,也就是指明当前锁的拥有者是当前线程。
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; }
lock方法:加锁
-
非公平锁模式,首先当前线程以CAS的方式,尝试将锁的状态从0修改成1,就是尝试获取锁。
-
获取到了就把当前线程设置给AQS的属性exclusiveOwnerThread,也就是指明当前锁的拥有者是当前线程。
-
当前锁已经被占用,线程会进入等待队列,不断地抢锁,抢到锁直接从等待队列弹出,否则判断线程的状态是否需要挂起(阻塞),这里循环抢锁,不断调用了尝试获取锁的方法,也利用了CAS思想。
// 非公平锁模式 lock = new ReentrantLock(); final void lock() { // 首先以 CAS 的方式,尝试将state 从0修改成1 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // compareAndSetState(0, 1)--> CAS 机制 protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } // acquire(1);--> CAS 机制 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//进入等待队列,继续不断尝试获取锁,直到抢到锁则弹出队列,否则判断线程的状态是否需要挂起 selfInterrupt(); } 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); } }
如果本文对你有帮助的话记得给一乐点个赞哦,感谢!