Lock锁

1. Lock 使用

参考博客:Java中Lock锁的基本使用

2. Lock 分析

1 Lock 和 synchronized 的不同点

  1. synchronized 是 Java 中的关键字, synchronized是内置的语言实现(虚拟机级);
    Lock是一个接口,需要程序员实现该Lock接口(API级);

  2. synchronized 发生异常时,会自动释放线程占有的锁;
    Lock 发生异常时,如果没有主动在finally中通过 unLock() 释放锁,可能造成死锁现象

  3. Lock 可以让等待锁的线程响应中断,线程可以中断去干别的事务,而 synchronized 却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  4. Lock 可以知道有没有成功获取锁,而synchronized却无法办到。

  5. Lock 可以提高多个线程进行读操作的效率。在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

2 主要的实现类

ReentrantLock
此类中有3个内部类,分别是Sync抽象同步器、NonfairSync非公平锁同步器、FairSync公平锁同步器。

abstract static class Sync extends AbstractQueuedSynchronizer {...}
static final class NonfairSync extends Sync{...}
static final class FairSync extends Sync {...}

Reentrant.lock()方法的调用过程
默认非公平
在这里插入图片描述

3 公平锁加锁过程

首先公平锁对应的是 ReentrantLock 内部静态类 FairSync

  1. 加锁时会先从 lock 方法中去获取锁,调用 AQS 中 的 acquire() 方法。
final void lock() {
            acquire(1);
        }

  1. acquire() 方法去调用了 tryAcquire() (FairSync 实现)方法。
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

protected final boolean tryAcquire(int acquires) {
            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;
        }

tryAcquire() 方法通过 getState() 获取当前同步状态,如果 state 为 0,则通过 CAS 设置该状态值,state 初始值为1,设置锁的拥有者为当前线程,tryAcquire返回true,否则返回false。如果同一个线程在获取了锁之后,再次去获取了同一个锁,状态值就加 1,释放一次锁状态值就减一,这就是可重入锁。只有线程 A 把此锁全部释放了,状态值减到0,其他线程才有机会获取锁。

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        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;
                }
            }
        }
    }
	

  1. 如果获取锁失败,也就是 tryAcquire 返回 false,则调用的 addWaiter(Node mode) 方法把该线程包装成一个 node 节点入同步队列(FIFO),即尝试通过 CAS 把该节点追加到队尾,如果修改失败,意味着有并发,同步器通过进入 enq 以死循环的方式来保证节点的正确添加,只有通过 CAS 将节点设置成为尾节点之后,当前线程才能从该方法中返回,否则当前线程不断的尝试设置。加入队列时,先去判断这个队列是不是已经初始化了,没有初始化,则先初始化,生成一个空的头节点,然后才是线程节点。
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);
        }
    }

  1. 加入了同步队列的线程,通过acquireQueued方法把已经追加到队列的线程节点进行阻塞,但阻塞前又通过 tryAccquire 重试是否能获得锁,如果重试成功能则无需阻塞)。
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;
    }


 private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.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);
    }

  1. 头节点在释放同步状态的时候,会调用unlock(),而unlock会调用release(),release() 会调用 tryRelease 方法尝试释放当前线程持有的锁(同步状态),成功的话调用unparkSuccessor() 唤醒后继线程,并返回true,否则直接返回false,
    注意:队列中的节点在被唤醒之前都处于阻塞状态。当一个线程节点被唤醒然后取得了锁,对应节点会从队列中删除 。

非公平锁也请参考

ThinkPHP的机制主要是为了处理并发环境下数据的一致性和防止资源的冲突。在ThinkPHP中,可以使用Lock类来实现机制,主要支持两种类型的:文件和Redis。 文件通过创建临时文件来定资源,适用于不支持操作的环境。它的使用非常简单,只需要实例化Lock类并调用lock方法即可。文件机制会创建一个文件,只有当该文件不存在时,才表示获取成功。 Redis则是利用Redis数据库的特性来实现机制,适用于支持Redis的环境。ThinkPHP的Redis支持设置的过期时间,这样即使在序异常退出的情况下,也能在指定时间后自动释放,避免造成死。使用Redis时,需要先配置好Redis客户端,然后实例化Lock类并调用lock方法时指定类型为'redis'。 两种的具体使用示例如下: ```php // 文件使用示例 $lock = new \think\lock\FileLock('lockfile.lock'); if ($lock->lock()) { // 成功获取后执行的操作 $lock->unlock(); } // Redis使用示例 $config = [ 'type' => 'redis', 'host' => 'localhost', // Redis服务器地址 'port' => 6379, // Redis服务器端口 'password' => '', // Redis服务器密码,如果设置了的话 'expire' => 10, // 的过期时间 ]; $redis = new \think\cache\driver\Redis($config); $lock = new \think\lock\RedisLock($redis, 'redis_lock_key'); if ($lock->lock()) { // 成功获取后执行的操作 $lock->unlock(); } ``` 注意,在使用机制时,需要确保在操作结束后调用unlock方法释放,否则会导致其他进或线无法获取到
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城有术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值