精华内容
下载资源
问答
  • 独占锁和共享锁

    2019-08-19 11:14:59
    多线程访问共享变量存在数据同时修改导致不一致问题,就需要来对共享数据的访问进行管理。 多个线程A B C 去竞争同一个L,存在线程获取的同步状态管理,排队获取,竞争获取,等待获取,释放唤醒其他...

    多线程访问共享变量存在数据同时修改导致不一致问题,就需要锁来对共享数据的访问进行管理。

    多个线程A B C 去竞争同一个锁L,存在线程获取锁的同步状态管理,排队获取锁,竞争获取锁,等待获取锁,释放锁唤醒其他等待中的线程问题。

    Java中的synchronized关键字是一种锁的实现,隐式的管理多线程与锁的问题,由JVM实现。

    JUC包下提供了显示锁来管理多线程和锁问题,Lock接口,队列同步器(AQS)AbstractQueuedSynchronizer分别定义了锁的操作接口和锁的实现基础模板。
    AQS同步器的模板方法基本分为3类,独占式的获取与释放同步状态,共享式的获取与释放同步状态,查询等待队列中的等待线程情况。

    独占锁获取了同步状态之后,其他独占和共享访问都会阻塞。 共享锁获取了同步状态之后,共享访问不会被阻塞,独占访问会被阻塞。

    独占锁的获取过程

    public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
    // FairSync尝试获取同步状态 
    protected final boolean tryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                // 当前状态为0 去尝试
                if (c == 0) {
                //  没有前驱节点并且获取成功  将当前线程设为持有锁的线程
                    if (!hasQueuedPredecessors() &&
                        compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                // 当前状态不为0  判断持有锁的线程是不是当前线程 是的话重新设置状态值 说明ReentrantLock.FairSync是可重入锁
                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 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);
            }
        }
    // 阻塞线程
    private final boolean parkAndCheckInterrupt() {
    		// 阻塞线程
            LockSupport.park(this);
            return Thread.interrupted();
        }
    
    

    acquire();同步器的获取独占锁的方法,先尝试获取同步状态即获取锁tryAcquire(arg) ,获取失败去创建得带队列节点然后加入到等待队列addWaiter(Node.EXCLUSIVE),等待队列节点自旋尝试去获取同步状态 acquireQueued()。获取同步状态失败之后阻塞该线程 LockSupport.park(this);。
    在这里插入图片描述

    private void doAcquireInterruptibly(int arg)
            throws InterruptedException {
            final Node node = addWaiter(Node.EXCLUSIVE);
            boolean failed = true;
            try {
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        // 响应中断 抛出异常 和普通独占获取不一样的点
                        throw new InterruptedException();
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    

    独占式获取,中断时仍然处于阻塞等待状态。如果是独占时中断响应式获取锁,中断后抛出异常InterruptedException();
    在这里插入图片描述

    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
            long lastTime = System.nanoTime();
            final Node node = addWaiter(Node.EXCLUSIVE);
            boolean failed = true;
            try {
            // 自旋尝试获取锁 
                for (;;) {
                    final Node p = node.predecessor();
                    // 前一节点是头结点尝试获取 都一样 独占式获取  独占响应中断式获取 独占超时获取 
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return true;
                    }
                    // 到达超时时间 返回失败
                    if (nanosTimeout <= 0)
                        return false;
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        nanosTimeout > spinForTimeoutThreshold)
                        // 超时时间未到 阻塞超时时间
                        LockSupport.parkNanos(this, nanosTimeout);
                        // 重新计算超时时间
                    long now = System.nanoTime();
                    nanosTimeout -= now - lastTime;
                    lastTime = now;
                    // 如果中断 抛出异常
                    if (Thread.interrupted())
                        throw new InterruptedException();
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    

    如果是超时获取锁,会在到达超时时间后直接返回false,如果该过程中中断仍然抛出异常InterruptedException();;
    在这里插入图片描述

    释放独占锁

    
     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;
                   // 完全释放之后 持有锁的线程设为null 
                    setExclusiveOwnerThread(null);
                }
                setState(c);
                return free;
            }
    
    // 唤醒后续节点
    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);
        }
    

    release()同步器的释放锁方法。释放锁的时候 先去尝试释放锁成功之后去唤醒后续节点的线程 LockSupport.unpark(s.thread);

    获取共享锁

    public final void acquireShared(int arg) {
    // 先尝试获取共享状态  
            if (tryAcquireShared(arg) < 0)
            // 尝试获取共享状态失败 自旋去尝试获取
                doAcquireShared(arg);
        }
    
    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) {
                    // 前驱节点为头结点  尝试获取同步状态
                        int r = tryAcquireShared(arg);
                        if (r >= 0) {、
                            // 获取成功 将当前节点设为头结点
                            setHeadAndPropagate(node, r);
                            p.next = null; // help GC
                            if (interrupted)
                                selfInterrupt();
                            failed = false;
                            return;
                        }
                    }
                    // 获取失败 设置状态为等待并阻塞线程
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    
    

    释放共享锁

    public final boolean releaseShared(int arg) {
    // 尝试释放 完全释放才会进行自旋尝试 唤醒后续线程
            if (tryReleaseShared(arg)) {
            // 共享锁尝试释放失败完全释放之后 
            doReleaseShared();
                return true;
            }
            return false;
        }
    
    private void doReleaseShared() {
            /*
             * Ensure that a release propagates, even if there are other
             * in-progress acquires/releases.  This proceeds in the usual
             * way of trying to unparkSuccessor of head if it needs
             * signal. But if it does not, status is set to PROPAGATE to
             * ensure that upon release, propagation continues.
             * Additionally, we must loop in case a new node is added
             * while we are doing this. Also, unlike other uses of
             * unparkSuccessor, we need to know if CAS to reset status
             * fails, if so rechecking.
             */
            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;
            }
        }
    
    展开全文
  • Lock锁底层依赖于AQS实现,AQS提供了多种锁的实现模式,其中独占锁和共享锁是主要的两种模式。AQS本身是一种模板方法设计模式,即AQS对外部提供了一些模板方法,而这些模板方法又会调用由子类实现的抽象方法。今天...

    Lock锁底层依赖于AQS实现,AQS提供了多种锁的实现模式,其中独占锁和共享锁是主要的两种模式。AQS本身是一种模板方法设计模式,即AQS对外部提供了一些模板方法,而这些模板方法又会调用由子类实现的抽象方法。今天我们主要是比较AQS中共享锁和独占锁的底层实现方面的不同。

    public final void acquire(int arg){/*对外提供的独占锁的模板方法*/ public final void acquireShared(int arg){ //对外提供的共享锁的模板方式

    if(!tryAcquire(arg) if(tryAcquireShared(arg)<0)

    &&acquireQueued(addWaiter(Node.EXCLUSIVE),arg)) doAcquireShared(arg);

    selfInterrupt()/*中断当前调用线程*/ }

    }

    先来分析acuqire(arg)方法,首先我们要理解java中的短路运算符&&,也就是说当tryAcquire(arg)方法返回false时,即获取锁失败时,才会执行acquireQueued(addWaiter(Node.EXCLUSIVE),arg),剖开语句acquireQueued(**),先执行addWaiter(Node.EXCLUSIVE),然后执行acquireQueued(),所以一句if基本上就调用了所有的后续处理,这种编码方式,在java源码实现中非常常见。相比之下,acquireShared(arg)方法更加符合我们平时的编码习惯。

    addWaiter方法的目的是将未成功获取到锁的线程中加入到同步队列中去,先看源码:

    private Node addWaiter(Node mode){ private Node enq(final Node node){

    Node node=new Node(Thread.currentThread(),mode); for(;;){

    Node pred=tail; Node t=tail;

    if(pred!=null){ if(t==null){

    node.prev=pred; if(compareAndSetHead(new Node()))

    if(compareAndSetTail(pred,node)){/*注意该方式是原子方式*/ tail=head;

    pred.next=node; }else{

    return node; node.prev=t;

    } if(compareAndSetTail(t,node)){

    } t.next=node;

    enq(node); return t;

    return node; }

    } }

    }

    }

    上述的addWaiter方法首先构造一个新的节点,并先尝试插入同步队列,如果成功后,直接返回,如果不成功,则调用enq方法进行循环插入。节点既然已经被加入到同步队列中去了,那么接下来就需要将线程阻塞,阻塞之前需要再次尝试获取锁,如果仍然失败则阻塞,具体的处理方法在acquireQueued(node,arg);

    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;

    failed=false;

    return interrupted;

    }

    if(shouldParkAfterFailedAcquire(p,node)&&parkAndCheckInterrupt())

    interrupted=true;

    }

    }finally{

    if(failed)

    cancelAcquire(node);

    }

    }

    在上述代码中,关键的一点是shouParkAfterFailedAcquire方法和parkAndCheckInterrupt方法,接下来我们看下这两个函数的源码实现:

    private static boolean shouldParkAfterFailedAcquire(Node pred,Node node){

    int ws=pred.waitStatus;

    if(ws==Node.SIGNAL) return true;// SIGNAL表示该节点的后继节点正在阻塞中,当该节点释放时,将唤醒后继节点。此时node可以安全地进行阻塞,因为可以保证会被唤醒

    if(ws>0){//表示前置节点已经被取消

    do{//循环找到一个未被取消的节点

    node.prev=pred=pred.prev;

    }while(pred.waitStatus>0);

    pred.next=node; //执行到这一句时,acquireQueued方法会循环一次,再次尝试获取锁

    }else{

    compareAndSetWaitStatus(pred,ws,Node.SIGNAL);

    }

    return false;

    }

    规则1:如果前继的节点状态为SIGNAL,表明当前节点可以安全地进行阻塞,则返回成功,此时acquireQueued方法的第12行(parkAndCheckInterrupt)将导致线程阻塞

    规则2:如果前继节点状态为CANCELLED(ws>0),说明前置节点已经被放弃,则回溯到一个非取消的前继节点,返回false,acquireQueued方法的无限循环将递归调用该方法,直至规则1返回true,导致线程阻塞

    规则3:如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,返回false后进入acquireQueued的无限循环,与规则2同

    下面我们再来分析一下,共享锁acquireShared()方法中的doAcquireShared(arg),调用该方法说明,共享锁已经用完了,当前线程需要进行等待重新获取:

    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){

    int r=tryAcquireShared(arg);//再次尝试获取共享锁

    if(r>=0){

    setHeadAndPropagate(node,r);//这一句很关键

    p.next=null;

    if(interrupted) selfInterrupt();

    failed=false;

    return;

    }

    if(shouldParkAfterFailedAcquire(p,node)&&parkAndCheckInterrupt())//同独占锁的规则一样

    interrupted=true;

    }

    }

    }finally{

    if(failed)

    cancelAcquire(node);

    }

    }

    上面的代码中主要的一句关键代码是setHeadAndPropagate方法,主要能够调用setHeadAndPropagate方法,说明当前线程已经活到了锁,下面我们来看看这句代码的实现:

    private void setHeadAndPropagate(Node node,int propagate){

    Node h=head;

    setHead(node);//因为有多个线程可能同时获取了共享锁,setHead方法可能会设置不成功,不过已经获取了锁,也不用关心是否设置成功

    if(propagate>0||h==null||h.waitStatus<0){

    Node s=node.next;

    if(s==null||s.isShared())

    doReleaseShared();

    }

    }

    独占锁某个节点被唤醒之后,它只需要将这个节点设置成head就完事了,而共享锁不一样,某个节点被设置为head之后,如果它的后继节点是SHARED状态的,那么将继续通过doReleaseShared方法尝试往后唤醒节点,实现了共享状态的向后传播。

    展开全文
  • java并发包提供的加锁模式分为独占锁和共享锁,独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock就是以独占方式实现的互斥锁。共享锁,则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。...

    1 锁的独占与共享
    java并发包提供的加锁模式分为独占锁和共享锁,独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock就是以独占方式实现的互斥锁。共享锁,则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。AQS的内部类Node定义了两个常量SHARED和EXCLUSIVE,他们分别标识 AQS队列中等待线程的锁获取模式。

    很显然,独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。 java的并发包中提供了ReadWriteLock,读-写锁。它允许一个资源可以被多个读操作访问,或者被一个 写操作访问,但两者不能同时进行。

    2 锁的公平与非公平
    锁的公平与非公平,是指线程请求获取锁的过程中,是否允许插队。在公平锁上,线程将按他们发出请求的顺序来获得锁;而非公平锁则允许在线程发出请求后立即尝试获取锁,如果可用则可直接获取锁,尝试失败才进行排队等待。ReentrantLock提供了两种锁获取方式,FairSyn和NofairSync。结论:ReentrantLock是以独占锁的加锁策略实现的互斥锁,同时它提供了公平和非公平两种锁获取方式。最初看源码时竟然把这两个概念弄混了。

    3 AQS提供的模板方法
    AQS提供了独占锁和共享锁必须实现的方法,具有独占锁功能的子类,它必须实现tryAcquire、tryRelease、isHeldExclusively等;共享锁功能的子类,必须实现tryAcquireShared和tryReleaseShared等方法,带有Shared后缀的方法都是支持共享锁加锁的语义。Semaphore是一种共享锁,ReentrantLock是一种独占锁。

    独占锁获取锁时,设置节点模式为Node.EXCLUSIVE
    

    public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();
    }
    共享锁获取锁,节点模式则为Node.SHARED

     private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        .....
    }
    

    4 对ConditionObject的认识
    ReentrantLock是独占锁,而且AQS的ConditionObject只能与ReentrantLock一起使用,它是为了支持条件队列的锁更方便。ConditionObject的signal和await方法都是基于独占锁的,如果线程非锁的独占线程,则会抛出IllegalMonitorStateException。例如signalAll源码:

        public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
        }
    

    我在想,既然Condtion是为了支持Lock的,为什么ConditionObject不作为ReentrantLock的内部类呢?对于实现锁功能的子类,直接扩展它就可以实现对条件队列的支持。但是,对于其它非锁语义的实现类如Semaphore、CountDownLatch等类来说,条件队列是无用的,也会给开发者扩展AQS带来困惑。总之,是各有利弊,大师们的思想,还需要仔细揣摩啊!

    展开全文
  • 这里的锁就是独占锁,也就是X锁。那么如果有查询这一行数据时,是要加锁吗?不是的,mysql为了提高性能,读的时候使用mvcc机制,通过ReadView,查询undo log版本链,获取到自己能查询到的版本...

    当有多个事务同时更新一条sql时,mysql是如何处理的呢?很显然,使用加锁的方式,一个事务获得了锁,进行操作,其他事务排队一个个等着,等当前这个事务执行完释放锁,其他事务获取锁取到锁的进行操作。这里的锁就是独占锁,也就是X锁。

    那么如果有查询这一行数据时,是要加锁吗?不是的,mysql为了提高性能,读的时候使用mvcc机制,通过ReadView,查询undo log版本链,获取到自己能查询到的版本数据。读与更新同时进行,不互斥,性能就提高了。

    共享锁又是什么呢?mysql里叫共享锁为S锁,通 过手动执行select * from table lock in share mode来加共享锁。加了共享锁,还能加独占锁吗?不能的,共享锁和独占锁是互斥的。不过共享锁和共享锁不互斥,也就是加了一个共享锁,还可以再加另一个共享锁。

    独占锁

    共享锁

    独占锁

    互斥

    互斥

    共享锁

    互斥

    不互斥

    但平时我们是很少去手动加共享锁的,一般更新时加独占锁就够了。 另外执行select * from table for update也会加独占锁。

    原文:https://www.cnblogs.com/ITyannic/p/12907721.html

    展开全文
  • 乐观锁和悲观锁:数据库的锁机制 在数据库管理系统中的并发控制是为了确保多个事务之间同时读取数据库中同一个数据是,不破坏事务的隔离性和统一性以及数据库的统一性。实现并发控制的手段大致可以分为乐观并发控制...
  • 文章目录1、公平锁和非公平锁2、可重入锁(递归锁)3、自旋锁3.1 手写一个自旋锁4、独占锁共享锁、互斥锁 1、公平锁和非公平锁 公平锁是指 多个线程按照申请锁的顺序来获取锁,根据先来后到的规则进行排队等候 。 ...
  • AQS全称AbstractQueueSynchronizer,在java.util.concurrent.locks包下,是构建锁和其他同步器的框架,它的定位是能够成为实现大部分同步需求的基础。Java中基于AQS的有ReentrantLock、Semaphore、读写锁、...
  • 一、公平与非公平1.1 概述公平:是指多个线程按照申请的顺序来获取。非公平:是指在多线程获取的顺序并不是按照申请的顺序,有可能后申请的线程比先申请的线程优先获取到,在高并发的情况下,有...
  • 是java.util.concurrent包下面各种各样的基础框架,供了排它模式和共享模式两种模式,比如ReentrantLock。结合他的名称我们可以基本得到以下几个关键点: 抽象:他是一个抽象类,定义了一些方法,也实现了一些...
  • 公平非公平公平:是指多个线程按照申请的顺序来获取值。在并发环境中,每一个线程在获取时会先查看此维护的等待队列,如果为空,或者当前线程是等待队列的第一个就占有,否者就会加入到等待队列中,以后...
  • AQS源码分析之独占锁和共享锁

    千次阅读 2017-07-13 15:06:45
    AQS实现机制并不是通过synchronized——给对象加锁实现的,事实上它仅仅是一个工具类!它没有使用更高级的机器指令,也不靠关键字,更不依靠JDK编译时的特殊处理,仅仅作为一个普普通通的类就完成了代码块的访问...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,672
精华内容 668
关键字:

独占锁和共享锁