1

Lock 锁底层实现 - 一乐乐

 1 year ago
source link: https://www.cnblogs.com/shan333/p/16726951.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

如果本文对你有帮助的话记得给一乐点个赞哦,感谢!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK