精华内容
下载资源
问答
  • 独占锁是线程独占的,同一时刻只有一个线程能拥有独占锁,AQS里将这个线程放置到exclusiveOwnerThread成员上去。...当然,如果一个线程刚释放了锁,不管是独占锁还是共享锁,都需要唤醒在后面等待的线程。

    前言

    在前面两篇系列文章中,已经讲解了独占锁的获取和释放过程,而共享锁的获取与释放过程也很类似,如果你前面独占锁的内容都看懂了,那么共享锁你也就触类旁通了。

    JUC框架 系列文章目录

    共享锁与独占锁的区别

    共享锁与独占锁最大的区别在于,共享锁的函数名里面都带有一个Shared(抖个机灵,当然不是这个)。

    • 独占锁是线程独占的,同一时刻只有一个线程能拥有独占锁,AQS里将这个线程放置到exclusiveOwnerThread成员上去。
    • 共享锁是线程共享的,同一时刻能有多个线程拥有共享锁,但AQS里并没有用来存储获得共享锁的多个线程的成员。
    • 如果一个线程刚获取了共享锁,那么在其之后等待的线程也很有可能能够获取到锁。但独占锁不会这样做,因为锁是独占的。
    • 当然,如果一个线程刚释放了锁,不管是独占锁还是共享锁,都需要唤醒在后面等待的线程。

    让我们把共享锁与独占锁的函数名都列出来看一下:

    独占锁 共享锁
    tryAcquire(int arg) tryAcquireShared(int arg)
    tryAcquireNanos(int arg, long nanosTimeout) tryAcquireSharedNanos(int arg, long nanosTimeout)
    acquire(int arg) acquireShared(int arg)
    acquireQueued(final Node node, int arg) doAcquireShared(int arg)
    acquireInterruptibly(int arg) acquireSharedInterruptibly(int arg)
    doAcquireInterruptibly(int arg) doAcquireSharedInterruptibly(int arg)
    doAcquireNanos(int arg, long nanosTimeout) doAcquireSharedNanos(int arg, long nanosTimeout)
    release(int arg) releaseShared(int arg)
    tryRelease(int arg) tryReleaseShared(int arg)
    - doReleaseShared()

    从上表可以看到,共享锁的函数是和独占锁是一一对应的,而且大部分只是函数名加了个Shared,从逻辑上看也是很相近的。

    doReleaseShared没有对应到独占锁的方法是因为它的逻辑是包含了unparkSuccessor,是建立在unparkSuccessor之上的,你可以简单地认为,doReleaseShared对应到独占锁的方法是unparkSuccessor。最主要的是,它们的使用时机不同:

    • 在独占锁中,释放锁时,会调用unparkSuccessor
    • 在共享锁中,获得锁和释放锁时,都会调用到doReleaseShared。不过获得共享锁时,是在一定条件下调用doReleaseShared

    观察Semaphore的内部类

    为了看到AQS的子类实现部分,我们从Semaphore看起。

        abstract static class Sync extends AbstractQueuedSynchronizer {
            Sync(int permits) {
                setState(permits);
            }
    
            final int nonfairTryAcquireShared(int acquires) {
                for (;;) {
                    int available = getState();
                    int remaining = available - acquires;
                    if (remaining < 0 ||
                        compareAndSetState(available, remaining))
                        return remaining;
                }
            }
    	}
    
        static final class NonfairSync extends Sync {
            protected int tryAcquireShared(int acquires) {
                return nonfairTryAcquireShared(acquires);
            }
        }
    
        static final class FairSync extends Sync {
            protected int tryAcquireShared(int acquires) {
                for (;;) {
                    if (hasQueuedPredecessors())
                        return -1;
                    int available = getState();
                    int remaining = available - acquires;
                    if (remaining < 0 ||
                        compareAndSetState(available, remaining))
                        return remaining;
                }
            }
        }
    
    • 首先看到Sync的构造器,看来参数permits是代表共享锁的数量。
    • 观察tryAcquireShared的公平和非公平锁的逻辑,发现区别只是 公平锁里面每次循环都会判断hasQueuedPredecessors()的返回值。

    这里先给大家讲一下tryAcquireShared
    参数acquires代表这次想要获得的共享锁的数量是多少。
    返回值则有三种情况:

    1. 如果返回值大于0,说明获取共享锁成功,并且后续获取也可能获取成功。
    2. 如果返回值等于0,说明获取共享锁成功,但后续获取可能不会成功。
    3. 如果返回值小于0,说明获取共享锁失败。

    直接看公平版本的tryAcquireShared,上面返回的地方:

    • hasQueuedPredecessors()如果返回了true,说明有线程排在了当前线程之前,现在公平版本又不能插队,所以结束返回-1,代表获取失败。
    • 如果remaining < 0成立,说明想要获取的共享锁数量已经超过了当前已有的数量,那么直接返回一个负数remaining,代表获取失败。
    • 如果remaining < 0不成立,说明想要获取的共享锁数量没有超过了当前已有的数量(等于0代表将会获取剩余所有的共享锁)。且接下来如果compareAndSetState(available, remaining)成功,那么返回一个>=0的数remaining,代表获取成功。

    接下来我们谈谈共享锁的tryAcquireShared和独占锁的tryAcquire的不同之处:

    • tryAcquire的返回值是boolean型,它只代表两种状态(获取成功或失败)。而tryAcquireShared的返回值是int型,如上有三种情况。
    • tryAcquireShared使用了自旋(死循环),但tryAcquire没有自旋。这将导致tryAcquire最多执行一次CAS操作修改同步器状态,但tryAcquireShared可能有多次。tryAcquireShared具体地讲,只要remaining>=0的(remaining < 0不成立),就一定会去尝试CAS设置同步器的状态。使用自旋的原因想必是,锁是共享的,既然还可能获取到(remaining>=0的),就一定要去尝试。
            protected final boolean tryReleaseShared(int releases) {
                for (;;) {
                    int current = getState();
                    int next = current + releases;
                    if (next < current) // overflow
                        throw new Error("Maximum permit count exceeded");
                    if (compareAndSetState(current, next))
                        return true;
                }
            }
    

    最后再看tryReleaseShared的实现,也用到了自旋操作,因为完全有可能多个线程同时释放共享锁,同时调用tryReleaseShared,所以需要用自旋保证 共享锁的释放最终能体现到同步器的状态上去。另外,除非int型溢出,那么此函数只可能返回true。

    共享锁的获取

    上面讲完了Semaphore的内部类,接下来我们就可以尽情地在AQS的源码里畅游了。

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    

    acquireShared对应到独占锁的方法是acquire

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

    咋一看感觉差别有点大,其实我们被迷惑了,后面我们会发现,之所以acquireShared里没有显式调用addWaiterselfInterrupt,是因为这两件事都被放到了doAcquireShared(arg)的逻辑里面了。

    接下来看看doAcquireShared方法的逻辑,它对应到独占锁是acquireQueued,除了上面提到的两件事,它们其实差别很少:

        private void doAcquireShared(int arg) {
            final Node node = addWaiter(Node.SHARED); //这件事放到里面来了
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head) {  //前驱是head时,才尝试获得共享锁
                        int r = tryAcquireShared(arg);
                        if (r >= 0) {  //获取共享锁成功时,才进行善后操作
                            setHeadAndPropagate(node, r);  //独占锁这里调用的是setHead
                            p.next = null; 
                            if (interrupted)
                                selfInterrupt(); //这件事也放到里面来了
                            failed = false;
                            return;
                        }
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    

    acquireQueued在获得独占锁成功时,执行的是:

    if (p == head && tryAcquire(arg)) {  // tryAcquire返回true,代表获取独占锁成功
        setHead(node);
        p.next = null; 
        failed = false;
        return interrupted;
    }
    

    所以对比发现,共享锁的doAcquireShared有两处不同:

    1. 创建的节点不同。共享锁使用addWaiter(Node.SHARED),所以会创建出想要获取共享锁的节点。而独占锁使用addWaiter(Node.EXCLUSIVE)
    2. 获取锁成功后的善后操作不同。共享锁使用setHeadAndPropagate(node, r),因为刚获取共享锁成功后,后面的线程也有可能成功获取,所以需要在一定条件唤醒head后继。而独占锁使用setHead(node)
        private void setHeadAndPropagate(Node node, int propagate) {
            Node h = head; 
            setHead(node);
            if (propagate > 0 || h == null || h.waitStatus < 0 ||
                (h = head) == null || h.waitStatus < 0) {
                Node s = node.next;
                if (s == null || s.isShared())
                    doReleaseShared();
            }
        }
    
        private void setHead(Node node) {
            head = node;
            node.thread = null;
            node.prev = null;
        }
    

    setHead函数只是将刚成为将成为head的节点变成一个dummy node。而setHeadAndPropagate里也会调用setHead函数。但是它在一定条件下还可能会调用doReleaseShared,看来这就是单词Propagate的由来了,也就是我们一直说的“如果一个线程刚获取了共享锁,那么在其之后等待的线程也很有可能能够获取到锁”。

    doReleaseShared留到之后讲解,因为共享锁的释放也会用到它。

    关于setHeadAndPropagate的详解请看这篇setHeadAndPropagate源码分析,主要有两张图帮助大家理解setHeadAndPropagate里的这个超长的if判断。

    共享锁的释放

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    

    releaseShared对应到独占锁的方法是release

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    

    可见独占锁的逻辑比较简单,只是在head状态不为0时,就唤醒head后继。

    而共享锁的逻辑则直接调用了doReleaseShared,但在获取共享锁成功时,也可能会调用到doReleaseShared。也就是说,获取共享锁的线程(分为:已经获取到的线程 即执行setHeadAndPropagate中、等待获取中的线程 即阻塞在shouldParkAfterFailedAcquire里)和释放共享锁的线程 可能在同时执行这个doReleaseShared

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
    

    我们来仔细分析下这个函数的逻辑:

    • 逻辑是一个死循环,每次循环中重新读取一次head,然后保存在局部变量h中,再配合if(h == head) break;,这样,循环检测到head没有变化时就会退出循环。注意,head变化一定是因为:acquire thread被唤醒,之后它成功获取锁,然后setHead设置了新head。而且注意,只有通过if(h == head) break;即head不变才能退出循环,不然会执行多次循环。
    • if (h != null && h != tail)判断队列是否至少有两个node,如果队列从来没有初始化过(head为null),或者head就是tail,那么中间逻辑直接不走,直接判断head是否变化了。
    • 如果队列中有两个或以上个node,那么检查局部变量h的状态:
      • 如果状态为SIGNAL,说明h的后继是需要被通知的。通过对CAS操作结果取反,将compareAndSetWaitStatus(h, Node.SIGNAL, 0)unparkSuccessor(h)绑定在了一起。说明了只要head成功得从SIGNAL修改为0,那么head的后继的代表线程肯定会被唤醒了。
      • 如果状态为0,说明h的后继所代表的线程已经被唤醒或即将被唤醒,并且这个中间状态即将消失,要么由于acquire thread获取锁失败再次设置head为SIGNAL并再次阻塞,要么由于acquire thread获取锁成功而将自己(head后继)设置为新head并且只要head后继不是队尾,那么新head肯定为SIGNAL。所以设置这种中间状态的head的status为PROPAGATE,让其status又变成负数,这样可能被 被唤醒线程(因为正常来讲,被唤醒线程的前驱,也就是head会被设置为0的,所以被唤醒线程发现head不为0,就会知道自己应该去唤醒自己的后继了) 检测到。
      • 如果状态为PROPAGATE,直接判断head是否变化。
    • 两个continue保证了进入那两个分支后,只有当CAS操作成功后,才可能去执行if(h == head) break;,才可能退出循环。
    • if(h == head) break;保证了,只要在某个循环的过程中有线程刚获取了锁且设置了新head,就会再次循环。目的当然是为了再次执行unparkSuccessor(h),即唤醒队列中第一个等待的线程。

    head状态为0的情况

    • 如果等待队列中只有一个dummy node(它的状态为0),那么head也是tail,且head的状态为0。
    • 等待队列中当前只有一个dummy node(它的状态为0),acquire thread获取锁失败了(无论独占还是共享),将当前线程包装成node放到队列中,此时队列中有两个node,但当前线程还没来得及执行一次shouldParkAfterFailedAcquire
    • 此时队列中有多个node,有线程刚释放了锁,刚执行了unparkSuccessor里的if (ws < 0) compareAndSetWaitStatus(node, ws, 0);把head的状态设置为了0,然后唤醒head后继线程,head后继线程获取锁成功,直到head后继线程将自己设置为AQS的新head的这段时间里,head的状态为0。
      • 具体地讲,如果是共享锁的话,一定是在调用unparkSuccessor之前就把head的状态变成0了,因为if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
      • 上面这种情况还可以继续延伸,在“唤醒head后继线程”后,head后继线程唤醒后第一次循环获取锁失败(你可能会疑问,上面的场景明明是刚有人释放了锁,为什么这里会失败,因为多线程环境下有可能被别的不公平获取方式插队了),调用shouldParkAfterFailedAcquire又将head设置回SIGNAL了,然后第二次循环开始之前(假设head后继线程此时分出去时间片),又有一个释放锁的线程在执行doReleaseShared里面的compareAndSetWaitStatus(h, Node.SIGNAL, 0)成功并且还unpark了处于唤醒状态的head后继线程,然后第二次循环开始(假设head后继线程此时得到时间片),获取锁成功。
        • 注意,如果unpark一个已经唤醒的线程,它的副作用是下一次park这个线程,线程不会阻塞。下下次park线程,才会阻塞。

    总结:

    • head状态为0的情况,属于一种中间状态。
    • 这种中间状态将变化为,head状态为SIGNAL,不管acquire thread接下来是获取锁成功还是失败。不过获取锁成功这种情况,需要考虑head后继(也就是包装acquire thread的那个node)不是队尾,如果是队尾,那么新head的状态也是为0的了。

    同时执行doReleaseShared

    这个函数的难点在于,很可能有多个线程同时在同时运行它。比如你创建了一个Semaphore(0),让N个线程执行acquire(),自然这多个线程都会阻塞在acquire()这里,然后你让另一个线程执行release(N)

    • 此时 释放共享锁的线程,肯定在执行doReleaseShared。
    • 由于 上面这个线程的unparkSuccessor,head后继的代表线程也会唤醒,进而执行doReleaseShared。
    • 重复第二步,获取共享锁的线程 又会唤醒 新head后继的代表线程。

    观察上面过程,有的线程 因为CAS操作失败,或head变化(主要是因为这个),会一直退不出循环。进而,可能会有多个线程都在运行该函数。doReleaseShared源码分析中的图解举例了一种循环继续的例子,当然,循环继续的情况有很多。

    总结

    • 共享锁与独占锁的最大不同,是共享锁可以同时被多个线程持有,虽然AQS里面没有成员用来保存持有共享锁的线程们。
    • 由于共享锁在获取锁和释放锁时,都需要唤醒head后继,所以将其逻辑抽取成一个doReleaseShared的逻辑了。
    展开全文
  • MySQL 共享锁与排他锁

    千次阅读 2017-06-14 13:53:26
    其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁), 直到已释放所有共享锁。 如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读...

    共享锁(Share Lock)

    共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁), 直到已释放所有共享锁。
    如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。

    用法

    SELECT ... LOCK IN SHARE MODE;

    在查询语句后面增加LOCK IN SHARE MODE,Mysql会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。

    排他锁(eXclusive Lock)

    排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。

    用法

    SELECT ... FOR UPDATE;

    在查询语句后面增加FOR UPDATE,Mysql会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。

    对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X);对于一般的Select语句,InnoDB不会加任何锁,事务可以通过以下语句给显示加共享锁或排他锁。

    • 共享锁:SELECT ... LOCK IN SHARE MODE;
    • 排他锁:SELECT ... FOR UPDATE;

    摘自网络

    展开全文
  • 独占锁与共享锁

    千次阅读 2019-08-09 19:38:11
    独占锁与共享锁前言概念引入独占锁概念共享锁概念源码分析ReentrantReadWriteLock源码读锁和写锁的具体加锁方式有什么区别 前言 独占锁和共享锁同样是一种概念。我们先介绍一下具体的概念,然后通过ReentrantLock和...

    前言

    独占锁和共享锁同样是一种概念。我们先介绍一下具体的概念,然后通过 ReentrantLock 和 ReentrantReadWriteLock 的源码来介绍独占锁和共享锁。

    概念引入

    独占锁概念

    独占锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排他锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和 JUC中Lock的实现类就是互斥锁。

    共享锁概念

    共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。 独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

    源码分析

    ReentrantReadWriteLock源码

    在这里插入图片描述
    我们看到 ReentrantReadWriteLock 有两把锁:ReadLock和WriteLock,见名知意,一个读锁一个写锁, 合称“读写锁”。
    再进一步观察可以发现 ReadLock 和 WriteLock 是靠内部类 Sync 实现的锁。
    Sync 是 AQS 的一个子类,这种结构在 CountDownLatch 、ReentrantLock 、Semaphore 里面也都存在。
    在ReentrantReadWriteLock 里面,读锁和写锁的锁主体都是 Sync ,但读锁和写锁的加锁方式不一样。
    读锁是共享锁,写锁是独占锁。读锁的共享锁可保证并发读非常高效,而读写、写读、写写的过程互斥,因为读锁和写锁是分离的。所以ReentrantReadWriteLock的并发性相比一般的互斥锁有了很大提升。

    读锁和写锁的具体加锁方式有什么区别

    在了解源码之前我们需要回顾一下其他知识。 在最开始提及 AQS 的时候我们也提到了state字段(int类型,32位),该字段用来描述有多少线程获持有锁。 在独享锁中这个值通常是0或者1(如果是重入锁的话state值就是重入的次数),在共享锁中state就是持有锁的数量。但是在 ReentrantReadWriteLock 中有读、写两把锁,所以需要在一个整型变量state上分别描述读锁和写锁的数量(或者也可以叫状态)。于是将state变量“按位切割”切分成了两个部分,高16位 表示读锁状态(读锁个数),低16位表示写锁状态(写锁个数)。如下图所示:
    在这里插入图片描述

    protected final boolean tryAcquire(int acquires) {
    	Thread current = Thread.currentThread();
    	int c = getState(); // 取到当前锁的个数
    	int w = exclusiveCount(c); // 取写锁的个数w
    	if (c != 0) { // 如果已经有线程持有了锁(c!=0)
    		// (Note: if c != 0 and w == 0 then shared count != 0)
    		if (w == 0 || current != getExclusiveOwnerThread()) // 如果写线程数(w)为0(换言之存在读锁) 或者持有锁的线程	不是当前线 程就返回失败
    				return false;
    		if (w + exclusiveCount(acquires) > MAX_COUNT) // 如果写入锁的数量大于最大数(65535,2的16次方-1)就抛出一个Error。
    		throw new Error("Maximum lock count exceeded");
    // Reentrant acquire
    		 setState(c + acquires);
    		return true;
     }
    if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) // 如果当且写线程数为0,并且当前线程需要阻塞那么就返回失败;或者如果通过CAS增加写线程数失败也返回失败。
    		return false;
    	setExclusiveOwnerThread(current); // 如果c=0,w=0或者c>0,w>0(重入),则设置当前线程或锁的拥有者
    	return true; 
    }
    
    • 这段代码首先取到当前锁的个数c,然后再通过c来获取写锁的个数w。因为写锁是低16位,所以取低16位的最大值与当前的c做与运算( int w = exclusiveCount©; ),高16位和0与运算后是0,剩下的就是低位运算的值,同时也是持 有写锁的线程数目。
    • 在取到写锁线程的数目后,首先判断是否已经有线程持有了锁。如果已经有线程持有了锁(c!=0),则查看当前写锁线程的数目,如果写线程数为0(即此时存在读锁)或者持有锁的线程不是当前线程就返回失败。
    • 如果写入锁的数量大于最大数(65535,2的16次方-1)就抛出一个Error。
    • 如果当且写线程数为0(那么读线程也应该为0,因为上面已经处理c!=0的情况),并且当前线程需要阻塞那么就返 回失败;如果通过CAS增加写线程数失败也返回失败。
    • 如果c=0,w=0或者c>0,w>0(重入),则设置当前线程或锁的拥有者,返回成功!

    tryAcquire()除了重入条件(当前线程为获取了写锁的线程)之外,增加了一个读锁是否存在的判断。
    如果存在读锁,则写锁不能被获取,原因在于:
    必须确保写锁的操作对读锁可见,如果允许读锁在已被获取 的情况下对写锁的获取,那么正在运行的其他读线程就无法感知到当前写线程的操作。
    因此,只有等待其他读线程都释放了读锁,写锁才能被当前线程获取,而写锁一旦被获取,则其他读写线程的后续访问均被阻塞。写锁的释放与 ReentrantLock 的释放过程基本类似,每次释放均减少写状态,当写状态为0时表示写锁已被释放,然后等待的读写线程才能够继续访问读写锁,同时前次写线程的修改对 后续的读写线程可见。 接着是读锁的代码:

    protected final int tryAcquireShared(int unused) {
     	Thread current = Thread.currentThread();
    	int c = getState();
    	if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)
    		return -1; // 如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态
    	int r = sharedCount(c);
    	if (!readerShouldBlock() &&r < MAX_COUNT &&compareAndSetState(c, c + SHARED_UNIT)) {
    		if (r == 0) {
     			firstReader = current;
     			firstReaderHoldCount = 1;
     		} else if (firstReader == current) {
     			firstReaderHoldCount++;
     		} else {
     			HoldCounter rh = cachedHoldCounter;
    			if (rh == null || rh.tid != getThreadId(current))
     				cachedHoldCounter = rh = readHolds.get();
    			else if (rh.count == 0)
     				readHolds.set(rh);
     				rh.count++;
     		}
    		return 1;
     	}
    	return fullTryAcquireShared(current);
    }
    

    可以看到在 tryAcquireShared(int unused) 方法中,如果其他线程已经获取了写锁,则当前线程获取读锁 失败,进入等待状态。如果当前线程获取了写锁或者写锁未被获取,则当前线程(线程安全,依靠CAS保证)增加读状态,成功获取读锁。读锁的每次释放(线程安全的,可能有多个读线程同时释放读锁)均减少读状态,减少的值是“1<<16”。所以读写锁才能实现读读的过程共享,而读写、写读、写写的过程互斥。

    展开全文
  • 共享锁

    千次阅读 2014-04-26 15:16:20
    其他用户可以并发读取数据,但任何事务都不能获取数据上的排他锁,直到已释放所有共享锁共享锁(S锁)又称为读锁,若事务T对数据对象A加上S锁,则事务T只能读A, 不能修改A;其他事务只能再对A加S锁,而不能加...

    释义编辑

    由非更新(读取)操作创建的锁。其他用户可以并发读取数据,但任何事务都不能获取数据上的排他锁,直到已释放所有共享锁
    共享锁(S锁)又称为读锁,若事务T对数据对象A加上S锁,则事务T只能读A, 不能修改A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

    2独占锁和共享锁的区别编辑

    1.共享锁(S锁):如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
    排他锁(X锁):如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
    2.Windows中可以有共享锁
    3.其它用户可以并发读取,查询数据。但不能修改,增加,删除数据。资源共享.[1]

    3共享锁的使用编辑

    在第一个连接中执行以下语句
    begin tran
    select * from table1 holdlock -holdlock人为加锁
    where B='b2'
    waitfor delay '00:00:30' --等待30秒
    commit tran
    在第二个连接中执行以下语句
    begin tran
    select A,C from table1
    where B='b2'
    update table1
    set A='aa'
    where B='b2'
    commit tran
    若同时执行上述两个语句,则第二个连接中的select查询可以执行
    而update必须等待第一个事务释放共享锁转为排它锁后才能执行 即要等待30秒 [2]
    展开全文
  • Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景 一、相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |--排他锁(X锁,MyISAM 叫做写锁...
  • 详述 MySQL 中的共享锁和排他锁

    千次阅读 2017-04-10 11:03:18
    在 MySQL 中的行级锁、表级锁和页级锁中,咱们介绍过,行级锁是 MySQL 中锁定粒度最细的一种锁...其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。如果事务T
  • 共享锁和排他锁

    千次阅读 2013-10-16 16:09:10
    共享锁(S锁): 如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。 排他锁(X锁): 如果事务T对数据A加上排他锁后,则其他事务不能再对A...
  • Mysql共享锁和排他锁

    千次阅读 多人点赞 2019-05-27 15:10:41
    不知道图片能不能正常显示 mysql锁机制分为表级锁和行级锁,...共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。 排他锁又称为写锁...
  • MySQL共享锁与排他锁

    千次阅读 2018-10-22 12:50:53
    mysql锁机制分为表级锁...共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。 排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其...
  • mysql 共享锁和排他锁的概念

    千次阅读 2019-03-12 22:42:23
    其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。 在查询语句后面增加LOCK IN SHARE MODE,MySQL 就会对查询结果中的每行都加共享锁,当没有其他线程对...
  • 共享锁 互斥锁 自旋锁

    千次阅读 2013-04-17 23:17:29
    共享锁:如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁,直到已释放所有共享锁。获准共享锁的事务只能读数据,不能修改数据。 排他锁:如果事务T对数据A加上排他锁后,则其他事务...
  • MySQL - 共享锁和排它锁初探

    千次阅读 2020-08-02 20:29:44
    文章目录共享锁 vs 排他锁SELECT ... FOR UPDATE 排它锁SELECT ... LOCK IN SHARE MODE 共享锁 共享锁 vs 排他锁 锁定某一行还可以用lock in share mode(共享锁) 和for update(排它锁) SELECT … FOR UPDATE 排它...
  • mysql共享锁与排他锁

    千次阅读 2016-12-05 11:27:23
    mysql锁机制分为表级锁和行级锁,本文就和大家分享一下我对mysql中行级锁中的共享锁与排他锁进行分享交流。 共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据...
  • mysql读锁(共享锁)与写锁(排他锁)

    万次阅读 多人点赞 2018-08-24 17:28:51
    读锁又称为共享锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。 写锁又称为排他锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务...
  • mysql中的共享锁与排他锁

    千次阅读 2017-02-16 17:08:27
    原文出处:MySQL中的共享锁与排他锁 在 MySQL中的行级锁,表级锁,页级锁中介绍过,行级锁是Mysql中锁定粒度最细的一种锁,行级锁能大大减少数据库操作的冲突。行级锁分为共享锁和排他锁两种,本文将详细介绍...
  • 共享锁与排他锁

    千次阅读 2013-12-24 09:54:12
    共享锁(S锁):如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。 获准共享锁的事务职能读取数据,不能修改数据。 排他锁(X锁):如果事务T对数据A加上排他锁后,则其他事务不能...
  • 1.共享锁(S)(读锁):一旦事务给数据或记录加上了共享锁(S),则只能读数据不能写数据,其他事务也只能加共享锁(S)知道上一个事务释放了锁 2.排它锁(X)(写锁):一旦被上上排它锁(X),该事务可进行读写...
  • Oracle的共享锁和排它锁

    千次阅读 2018-08-28 15:00:02
    oracle有两种模式的锁:排他锁(exclusive lock,即X锁)和共享锁(share lock,即S锁)。 共享锁:如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能...
  • 什么共享锁:简单来说就是该锁锁定的资源只能进行读取,不能进行增删改操作,直到该锁释放。 example 1: 请求1:select * from tabletest 请求2:update tabletast set .... 数据库执行请求1的sql语句,是...
  • 共享锁与排他锁理解

    千次阅读 2016-03-17 15:04:23
    共享锁(S锁):如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。 排他锁(X锁):如果事务T对数据A加上排他锁后,则其他事务不能再对A加任...
  • AQS共享锁的实现原理

    千次阅读 2017-10-20 00:44:45
    一、AQS共享锁的实现原理前面的文章Lock的实现中分析了AQS独占锁的实现原理,那么接下来就分析下AQS是如何实现共享锁的。共享锁的介绍共享锁:同一时刻有多个线程能够获取到同步状态。那么它是如何做到让多个线程...
  • 由于等待一个锁定线程只有在获得这把之后,才能恢复运行,所以让持有的线程在不需要时候及时释放锁是很重要的。在以下情况下,持有的线程会释放锁: 1. 执行完同步代码块。 2. 在执行同步代码块的过程中...
  • JDK AQS 共享锁部分的源码详解,详细介绍了 AQS 共享锁的加锁和释放流程
  • JAVA共享锁和排他锁总结

    千次阅读 2020-05-11 17:32:16
    共享锁和排他锁总结1.ReentrantReadWriteLock2.锁申请和释放策略3.插队策略4.升降级策略5.使用场合总结 1.ReentrantReadWriteLock 实现了ReadWriteLock接口,最主要的有两个方法:readLock()和writeLock()用来获取读...
  • 共享锁(S锁)和排它锁(X锁)详解

    千次阅读 2017-10-26 11:22:57
    共享锁(S锁):如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。 排他锁(X锁):如果事务T对数据A加上排他锁后,则其他事务不能再对A加任...
  • 重要声明:本人之前对java中的读写锁也不是非常了解,用的也不是很多,尤其在读写锁的策略原理一块没有深究过,本篇文章是在学习【玩...在【ReentrantLock锁详解】一文中讲到了java中锁的划分,本篇主要讲述共享锁和...
  • 数据库中的共享锁和排他锁

    千次阅读 2018-07-27 14:26:38
    在 MySQL 中的行级锁、表级锁和页级锁中,咱们介绍过,行级锁是 MySQL 中锁定粒度最细的一种锁,行级...其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。...
  • mysql悲观锁中的共享锁和排他锁

    千次阅读 2017-06-04 00:15:35
    共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。 排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取...
  • MySQL之共享锁与排它锁

    千次阅读 2021-03-09 19:08:04
    共享锁和排它锁

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 202,364
精华内容 80,945
关键字:

共享锁什么时候释放