锁机制(四)

本小节介绍锁释放Lock.unlock()。

Release/TryRelease

unlock操作实际上就调用了AQS的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;
}

前面提到过tryRelease(arg)操作,此操作里面总是尝试去释放锁,如果成功,说明锁确实被当前线程持有,那么就看AQS队列中的头结点是否为空并且能否被唤醒,如果可以的话就唤醒继任节点(下一个非CANCELLED节点,下面会具体分析)。

对独占锁,java.util.concurrent.locks.ReentrantLock.Sync.tryRelease(int) 释放锁的过程。

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//将AQS状态位减少要释放的次数(对于独占锁而言总是1
    if (Thread.currentThread() != getExclusiveOwnerThread())//判断持有锁的线程是否是当前线程
        throw new IllegalMonitorStateException();//如果不是就抛出IllegalMonitorStateExeception()
    boolean free = false;
    if (c == 0) {    //如果剩余的状态位0(也就是没有线程持有锁)
        free = true;//
        setExclusiveOwnerThread(null);//当前线程是最后一个持有锁的线程,清空AQS持有锁的独占线程
    }
    setState(c);//将剩余的状态位写回AQS,如果没有线程持有锁就返回true,否则就是false
    return free;
}

参考上一节的分析就可以知道,这里c==0决定了是否完全释放了锁。由于ReentrantLock是可重入锁,因此同一个线程可能多重持有锁,那么当且仅当最后一个持有锁的线程释放锁是才能将AQS中持有锁的独占线程清空,这样接下来的操作才需要唤醒下一个需要锁的AQS节点(Node),否则就只是减少锁持有的计数器,并不能改变其他操作。

tryRelease操作成功后(也就是完全释放了锁),release操作才能检查是否需要唤醒下一个继任节点。这里的前提是AQS队列的头结点需要锁(waitStatus!=0),如果头结点需要锁,就开始检测下一个继任节点是否需要锁操作。

在上一节中说道acquireQueued操作完成后(拿到了锁),会将当前持有锁的节点设为头结点,所以一旦头结点释放锁,那么就需要寻找头结点的下一个需要锁的继任节点,并唤醒它。

private void unparkSuccessor(Node node) {
        //此时node是需要是需要释放锁的头结点

        //清空头结点的waitStatus,也就是不再需要锁了
        compareAndSetWaitStatus(node, Node.SIGNAL, 0);

        //从头结点的下一个节点开始寻找继任节点,当且仅当继任节点的waitStatus<=0才是有效继任节点,否则将这些waitStatus>0(也就是CANCELLED的节点)从AQS队列中剔除
       //这里并没有从head->tail开始寻找,而是从tail->head寻找最后一个有效节点。
       //解释在这里 http://www.blogjava.net/xylz/archive/2010/07/08/325540.html#377512

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

公平锁和非公平锁只是在获取锁的时候有差别,其它都是一样的。

在上面非公平锁的代码中总是优先尝试当前是否有线程持有锁,一旦没有任何线程持有锁,那么非公平锁就霸道的尝试将锁“占为己有”。如果在抢占锁的时候失败就和公平锁一样老老实实的去排队。

也即是说公平锁和非公平锁只是在入AQSCLH队列之前有所差别,一旦进入了队列,所有线程都是按照队列中先来后到的顺序请求锁。

时间: 2024-10-16 06:37:29

锁机制(四)的相关文章

[数据库事务与锁]详解四: 数据库的锁机制

注明: 本文转载自http://www.hollischuang.com/archives/898 数据库的读现象浅析中介绍过,在并发访问情况下,可能会出现脏读.不可重复读和幻读等读现象,为了应对这些问题,主流数据库都提供了锁机制,并引入了事务隔离级别的概念. 并发控制 在计算机科学,特别是程序设计.操作系统.多处理机和数据库等领域,并发控制(Concurrency control)是确保及时纠正由并发操作导致的错误的一种机制. 数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存

Sql Server之旅——第十四站 深入的探讨锁机制

原文:Sql Server之旅--第十四站 深入的探讨锁机制 上一篇我只是做了一个堆表让大家初步的认识到锁的痉挛状态,但是在现实世界上并没有这么简单的事情,起码我的表不会没有索引对吧,,,还 有就是我的表一定会有很多的连接过来,10:1的读写,很多码农可能都会遇到类似神乎其神的死锁,卡住,读不出来,插不进入等等神仙的事情导致性 能低下,这篇我们一起来探讨下. 一: 当select遇到性能低下的update会怎么样? 1. 还是使用原始的person表,插入6条数据,由于是4000字节,所以两条数

数据库并发事务控制四:postgresql数据库的锁机制二:表锁

在博文<数据库并发事务控制四:postgresql数据库的锁机制 > http://blog.csdn.net/beiigang/article/details/43302947 中后面提到: 常规锁机制可以参考pg的官方手册,章节和内容见下面 13.3. Explicit Locking http://www.postgresql.org/docs/9.4/static/explicit-locking.html 这节分为:表锁.行锁.页锁.死锁.Advisory锁(这个名字怎么翻译好???

锁机制与原子操作 &lt;第四篇&gt;

一.线程同步中的一些概念 1.1临界区(共享区)的概念 在多线程的环境中,可能需要共同使用一些公共资源,这些资源可能是变量,方法逻辑段等等,这些被多个线程共用的区域统称为临界区(共享区),临界区的资源不是很安全,因为线程的状态是不定的,所以可能带来的结果是临界区的资源遭到其他线程的破坏,我们必须采取策略或者措施让共享区数据在多线程的环境下保持完成性不让其受到多线程访问的破坏. 1.2基元用户模式 基元用户模式是指使用cpu的特殊指令来调度线程,所以这种协调调度线程是在硬件中进行的所以得出了它第一

并发编程(四):也谈谈数据库的锁机制

http://www.2cto.com/database/201403/286730.html 1. 数据库并发的问题 数据库带来的并发问题包括: 1. 丢失更新. 2. 未确认的相关性(脏读). 3. 不一致的分析(非重复读). 4. 幻像读. 详细描述如下: 1.1.丢失更新 当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题.每个事务都不知道其它事务的存在.最后的更新将重写由其它事务所做的更新,这将导致数据丢失. e.g.事务A和事务B同时修改某行的值, 事务A

数据库并发事务控制四:postgresql数据库的锁机制

并发控制是DBMS的关键技术,并发控制技术也称为同步机制,其实现通常依赖于底层的并发控制机制.操作系统提供了多种同步对象,如事件 Event.互斥锁 Mutex和条件变量 Cond.信号量Semaphore.读写锁 RWLock.自旋锁 Spinlock等.数据库管理系统自己实现封锁主要是考虑: 锁语义加强:OS只提供排它锁.为了提高并发度,数据库至少需要共享锁和排它锁,即读锁和写锁: 锁的功能增强:数据库提供视图监测封锁情况和进行死锁检测: 可靠性的考虑:例如,在某个持锁进程任意一点回滚时,数

python3.4多线程实现同步的四种方式(锁机制、条件变量、信号量和同步队列)

临界资源即那些一次只能被一个线程访问的资源,典型例子就是打印机,它一次只能被一个程序用来执行打印功能,因为不能多个线程同时操作,而访问这部分资源的代码通常称之为临界区. threading的Lock类,用该类的acquire函数进行加锁,用realease函数进行解锁 import threading import time class Num: def __init__(self): self.num = 0 self.lock = threading.Lock() def add(self)

【转】数据库锁机制

1 前言 数据库大并发操作要考虑死锁和锁的性能问题.看到网上大多语焉不详(尤其更新锁),所以这里做个简明解释,为下面描述方便,这里用T1代表一个数据库执行请求,T2代表另一个请求,也可以理解为T1为一个线程,T2 为另一个线程.T3,T4以此类推.下面以SQL Server(2005)为例. 2 锁的种类 共享锁(Shared lock). 例1: ---------------------------------------- T1: select * from table (请想象它需要执行

java的锁机制

一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池等待队列中). 取到锁后,他就开始执行同步代码(被synchronized修饰的代码):线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中等待的某个线程就可以拿到锁执行同步代码了.这样就保证了同步代码在统一时刻只有一个线程在执行. 众所周知,在Java多线程编程中

SqlServer锁机制与实践

在如今这个云计算,大数据,移动互联网大行其道的时代,各种NoSQL数据库MongoDb.redis.HBase等使用的越来越广泛,大有替代关系型数据库的趋势.但是关系型数据库真的已经落伍了吗?答案是否定的.非关系型数据库不支持ACID属性,不支持事务,无法适应复杂查询的缺点.关系型数据库凭借其强一致性的特点,注定了在类似银行转账,订单支付等场景中,还是唯一的选择.众所周知,SQLSERVER通过锁来提供ACID属性,处理并发访问,本文尝试通过对锁机制的一些学习,让我们明白数据库查询超时,死锁等问