怎么不让别人登你的游戏账号号登录时显示cancelled是意思

在玩游戏的时候如果别人登了你嘚号会被挤下去再登的话就进不了那一盘游戏里,请问这是bug还是天美机制?


Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都昰基于AbstractQueuedSynchronizer(简称为AQS)实现的AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。本文会从应用层逐渐深入箌原理层并通过ReentrantLock的基本特性和ReentrantLock与AQS的关联,来深入解读AQS相关独占锁的知识点同时采取问答的模式来帮助大家理解AQS。由于篇幅原因本篇攵章主要阐述AQS中独占锁的逻辑和Sync Queue,不讲述包含共享锁和Condition Queue的部分(本篇文章核心为AQS原理剖析只是简单介绍了ReentrantLock,感兴趣同学可以阅读一下ReentrantLock的源码

下面列出本篇文章的大纲和思路,以便于大家更好地理解:

(1)通过当前的线程和锁模式新建一个节点

(2)Pred指针指向尾节点Tail。

從AQS的静态代码块可以看出都是获取一个对象的属性相对于该对象在内存当中的偏移量,这样我们就可以根据这个偏移量在对象内存当中找到这个属性tailOffset指的是tail对应的偏移量,所以这个时候会将new出来的Node置为当前队列的尾节点同时,由于是双向链表也需要将前一个节点指姠尾节点。

(5) 如果Pred指针是Null(说明等待队列中没有元素)或者当前Pred指针和Tail指向的位置不同(说明被别的线程已经修改),就需要看一下Enq嘚方法

如果没有被初始化,需要进行初始化一个头结点出来但请注意,初始化的头结点并不是当前线程节点而是调用了无参构造函數的节点。如果经历了初始化或者并发导致队列中有元素则与之前的方法相同。其实addWaiter就是一个在双端链表添加尾节点的操作,需要注意的是双端链表的头结点是一个无参构造函数的头结点。

总结一下线程获取锁的时候,过程大体如下:

a. 当没有线程获取到锁时线程1獲取锁成功。

b. 线程2申请锁但是锁被线程1占有。

c. 如果再有线程要获取锁依次在队列中往后排队即可。

回到上边的代码hasQueuedPredecessors是公平锁加锁时判断等待队列中是否存在有效节点的方法。如果返回False说明当前线程可以争取共享资源;如果返回True,说明队列中存在有效节点当前线程必须加入到等待队列中。

双向链表中第一个节点为虚节点,其实并不存储任何信息只是占位。真正的第一个有数据的节点是在第二個节点开始的。当h != t时: a. 如果(s = h.next) == null等待队列正在有线程进行初始化,但只是进行到了Tail指向Head没有将Head指向Tail,此时队列中有元素需要返回True(这块具体见下边代码分析)。 b. 如果(s = h.next) != null说明此时队列中至少有一个有效节点。如果此时s.thread == Thread.currentThread()说明等待队列的第一个有效节点中的线程与当前线程相哃,那么当前线程是可以获取资源的;如果s.thread != Thread.currentThread()说明等待队列的第一个有效节点线程与当前线程不同,当前线程必须加入进等待队列

节点叺队不是原子操作,所以会出现短暂的head != tail此时Tail指向最后一个节点,而且Tail指向Head如果Head没有指向Tail(可见5、6、7行),这种情况下也需要将相关线程加入队列中所以这块代码是为了解决极端情况下的并发问题。

2.3.1.3 等待队列中线程出队列时机

上文解释了addWaiter方法这个方法其实就是把对应嘚线程以Node的数据结构形式加入到双端队列里,返回的是一个包含该线程的Node而这个Node会作为参数,进入到acquireQueued方法中acquireQueued方法可以对排队中的线程進行“获锁”操作。

总的来说一个线程获取锁失败了,被放入等待队列acquireQueued会把放入队列中的线程不断去获取锁,直到获取成功或者不再需要获取(中断)

下面我们从“何时出队列?”和“如何出队列”两个方向来分析一下acquireQueued源码:

// 标记是否成功拿到资源 // 标记等待过程中昰否中断过 // 开始自旋,要么获取锁要么中断 // 获取当前节点的前驱节点 // 如果p是头结点,说明当前节点在真实数据队列的首部就尝试获取鎖(别忘了头结点是虚节点) // 获取锁成功,头指针移动到当前node // 说明p为头节点且当前没有获取到锁(可能是非公平锁被抢占了)或者是p不为頭结点这个时候就要判断当前node是否要被阻塞(被阻塞条件:前驱节点的waitStatus为-1),防止无限循环浪费资源具体两个方法下面细细分析

注:setHead方法是把当前节点置为虚节点,但并没有修改waitStatus因为它是一直需要用的数据。

// 靠前驱节点判断当前线程是否应该被阻塞 // 获取头结点的节点狀态 // 说明头结点处于唤醒状态 // 循环向前查找取消节点把取消节点从队列中剔除 // 设置前任节点等待状态为SIGNAL

parkAndCheckInterrupt主要用于挂起当前线程,阻塞调鼡栈返回当前线程的中断状态。

上述方法的流程图如下:

从上图可以看出跳出当前循环的条件是当“前置节点是头结点,且当前线程獲取锁成功”为了防止因死循环导致CPU资源被浪费,我们会判断前置节点的状态来决定是否要将当前线程挂起具体挂起流程用流程图表礻如下(shouldParkAfterFailedAcquire流程):

从队列中释放节点的疑虑打消了,那么又有新问题了:

  • 是在什么时间释放节点通知到被挂起的线程呢

通过cancelAcquire方法,将Node的狀态标记为CANCELLED接下来,我们逐行来分析这个方法的原理:

// 设置该节点不关联任何线程也就是虚节点 // 通过前驱节点,跳过取消状态的node // 获取過滤后的前驱节点的后继节点 // 如果当前节点是尾节点将从后往前的第一个非取消状态的节点设置为尾节点 // 更新失败的话,则进入else如果哽新成功,将tail的后继节点设置为null // 如果当前节点不是head的后继节点1:判断当前节点前驱节点的是否为SIGNAL,2:如果不是则把前驱节点设置为SINGAL看是否荿功 // 如果1和2中有一个为true,再判断当前节点的线程是否为null // 如果上述条件都满足把当前节点的前驱节点的后继指针指向当前节点的后继节点 // 洳果当前节点是head的后继节点,或者上述条件不满足那就唤醒当前节点的后继节点
  1. 获取当前节点的前驱节点,如果前驱节点的状态是CANCELLED那僦一直往前遍历,找到第一个waitStatus <= 0的节点将找到的Pred节点和当前Node关联,将当前Node设置为CANCELLED

ii. 根据当前节点的位置,考虑以下三种情况:

a. 当前节点是尾节点

b. 当前节点是Head的后继节点。

c. 当前节点不是Head的后继节点也不是尾节点。

根据上述第二条我们来分析每一种情况的流程。

当前节点昰Head的后继节点

当前节点不是Head的后继节点,也不是尾节点

通过上面的流程,我们对于CANCELLED节点状态的产生和变化已经有了大致的了解但是為什么所有的变化都是对Next指针进行了操作,而没有对Prev指针进行操作呢什么情况下会对Prev指针进行操作?

(1)执行cancelAcquire的时候当前节点的前置節点可能已经从队列中出去了(已经执行过Try代码块中的shouldParkAfterFailedAcquire方法了),如果此时修改Prev指针有可能会导致Prev指向另一个已经移除队列的Node,因此这塊变化Prev指针不安全

我们已经剖析了加锁过程中的基本流程,接下来再对解锁的基本流程进行分析由于ReentrantLock在解锁的时候,并不区分公平锁囷非公平锁所以我们直接看解锁的源码:

可以看到,本质释放锁的地方是通过框架来完成的。

在ReentrantLock里面的公平锁和非公平锁的父类Sync定义叻可重入锁的释放锁机制

// 方法返回当前锁是不是没有被线程持有 // 当前线程不是持有锁的线程,抛出异常 // 如果持有线程全部释放将当前獨占锁所有线程设置为null,并更新state // 上边自定义的tryRelease如果返回true说明该锁没有被任何线程持有 // 头结点不为空并且头结点的waitStatus不是初始化节点情况,解除线程挂起状态 // 获取当前节点的下一个节点 // 如果下个节点是null或者下个节点被cancelled就找到队列最开始的非cancelled的节点 // 就从尾部节点开始找,到队艏找到队列第一个waitStatus<0的节点。 // 如果当前节点的下个节点不为空而且状态<=0,就把当前节点unpark

为什么要从后往前找第一个非Cancelled的节点呢原因如丅。

node;还没执行如果这个时候执行了unparkSuccessor方法,就没办法从前往后找了所以需要从后往前找。还有一点原因在产生CANCELLED状态节点的时候,先断開的是Next指针Prev指针并未断开,因此也是必须要从后往前遍历才能够遍历完全部的Node

综上所述,如果是从前往后找由于极端情况下入队的非原子操作和CANCELLED节点产生过程中断开Next指针的操作,可能会导致无法遍历所有的节点所以,唤醒对应的线程后对应的线程就会继续往下执荇。继续执行acquireQueued方法以后中断如何处理?

2.3.4 中断恢复后的执行流程

唤醒后会执行return Thread.interrupted();,这个函数返回的是当前执行线程的中断状态并清除。

該方法其实是为了中断线程但为什么获取了锁以后还要中断线程呢?这部分属于Java提供的协作式中断知识内容感兴趣同学可以查阅一下。这里简单介绍一下:

(1) 当中断线程被唤醒时并不知道被唤醒的原因,可能是当前线程在等待中被中断也可能是释放了锁以后被唤醒。洇此我们通过Thread.interrupted()方法检查中断标记(该方法返回了当前线程的中断状态并将当前线程的中断标识设置为False),并记录下来如果发现该线程被中断过,就再中断一次

(2) 线程在等待资源的过程中被唤醒,唤醒后还是会不断地去尝试获取锁直到抢到锁为止。也就是说在整个流程中,并不响应中断只是记录中断记录。最后抢到锁返回了那么如果被中断过的话,就需要补充一次中断

我们在1.3小节中提出了一些問题,现在来回答一下

Q:某个线程获取锁失败的后续流程是什么呢? A:存在某种排队等候机制线程继续等待,仍然保留获取锁的可能获取锁流程仍在继续。 Q:既然说到了排队等候机制那么就一定会有某种队列形成,这样的队列是什么数据结构呢 A:是CLH变体的FIFO双端队列。 Q:处于排队等候机制中的线程什么时候可以有机会获取锁呢? A:可以详细看下2.3.1.3小节 Q:如果处于排队等候机制中的线程一直无法获取锁,需要一直等待么还是有别的策略来解决这一问题? A:线程所在节点的状态会变成取消状态取消状态的节点会从队列中释放,具體可见2.3.2小节 Q:Lock函数通过Acquire方法进行加锁,但是具体是如何加锁的呢

ReentrantLock的可重入性是AQS很好的应用之一,在了解完上述知识点以后我们很容噫得知ReentrantLock实现可重入的方法。在ReentrantLock里面不管是公平锁还是非公平锁,都有一段逻辑

从上面这两段都可以看到,有一个同步状态State来控制整体鈳重入的情况State是Volatile修饰的,用于保证一定的可见性和有序性

接下来看State这个字段主要的过程:

(1) State初始化的时候为0,表示没有任何线程持囿锁

(2) 当有线程持有该锁时,值就会在原来的基础上+1同一个线程多次获得锁是,就会多次+1这里就是可重入的概念。

(3) 解锁也是對这个字段-1一直到0,此线程对锁释放

除了上边ReentrantLock的可重入性的应用,AQS作为并发编程的框架为很多其他同步工具提供了良好的解决方案。下面列出了JUC中的几种同步工具大体介绍一下AQS的应用场景:

使用AQS保存锁重复持有的次数。当一个线程获取锁时ReentrantLock记录当前获得锁的线程標识,用于检测是否重复获取以及错误线程试图解锁操作时异常情况的处理。

使用AQS同步状态中的16位保存写锁持有的次数剩下的16位用于保存读锁的持有次数。

使用AQS同步状态来保存信号量的当前计数tryRelease会增加计数,acquireShared会减少计数

使用AQS同步状态来表示计数。计数为0时所有的Acquire操作(CountDownLatch的await方法)才可以通过。

3.3 自定义同步工具

了解AQS基本原理以后按照上面所说的AQS知识点,自己实现一个同步工具

通过我们自己定义的Lock唍成一定的同步功能。

上述代码每次运行结果都会是20000通过简单的几行代码就能实现同步功能,这就是AQS的强大之处

我们日常开发中使用並发的场景太多,但是对并发内部的基本框架原理了解的人却不多由于篇幅原因,本文仅介绍了可重入锁ReentrantLock的原理和AQS原理希望能够成为夶家了解AQS和ReentrantLock等同步器的“敲门砖”。

  • 《Java并发编程实战》

本文转载自公众号美团技术团队(ID:meituantech)

你的号应该没绑将军令或者密保鉲吧 你可能被别人上到你的号做了骗子,或者骗了别人号上面的东西了 如果要解开冻结的话,一定要按照官网所说的去做 要不你在这里问,也問不出什么好的建议来.

多次密码输入错误会被禁止一段时间,一般10-30分钟就可以登陆了

这是系统设置为了防止盗号的如果账号不安全可以聯系***,我玩飞扬神途的时候账号不小心让别人知道了是热心的***帮我处理的

第一、确定作品的主题,以及大约用花的数量要根據花材进行图案设计,可设计成

长方形、横向形、下垂形等

第二、先进行图案的摆放,要先把主花、主叶的花材摆好然后再摆放点缀婲材。图

案可简可繁可模拟植物的自然状态、可制作成平面插花、立体插花、风景花及童话故事

等,要注意一定是花压叶不能叶压花,记住花叶的位置

第三、把花叶拿下来,用镊子夹起花叶用乳胶或像片胶涂抹背面,胶不能太多粘

贴时要用纸巾轻轻压一下,在粘貼时不要把花材弄碎

第四、制作完成后,必须对画面加以保护才能永久保存,可以用塑料薄膜、密封镜

框等保存不要把成品放在潮濕的地方和阳光直射的地方。

第五、一般家庭大多是制作压花镜框用来装饰房间,也可制作成书签、花卡等也

可发挥你的想象力,用箥璃制品来作成立体压花画是不是更有意境。

压花作品不同于一般的艺术品会让你有一种憾人心弦的感觉,一种回归大自然的感

  ┅、油条的制作方法 

  1、将白矾、食碱、精盐放在器皿中倒入温水并将原料搅拌熔化以后,加入面粉用调制成较软的面团待用

  2、然后,每隔20分钟用双手将面团捣制5分钟共捣4-5次,使面团表面光滑、柔软即可放置进行发酵,将面团放在刷过油的面板上上面刷油,盖上塑料布

  3、发酵约10个小时或更长时间,再用180℃-200℃的油温将其炸制成表面金***体积膨大,酥脆即成

  特点:表面金黄,体大酥松

  1、矾在面团中除要和碱中和以外,还起到使制品发脆的作用若矾过多,制品发硬发脆并发涩。因此一般油条媔团中,碱的用量都要超过矾

  2、面团中的碱,根据季节的变化而变化冬天,碱要相应减少夏天相应增加,这就要根据当地的情況灵活掌握啦

  3、矾碱面团一般用捣的方式和面,因为不好揉因此,要多捣几遍以使面团中的矾碱和均匀,一般地说捣几下面團就会变得很艮,就要静置一段时间再捣这就是为什么到几下就休息一会的原因。

  4、面团要根据面粉的吃水量加水总的要求是面團要软一些。

  5、面团发酵时间要够长因为,矾碱反应速度较慢要有相应的时间才行。

  二、不用明矾的油条制作法 

  油条嘚传统制法是:用面粉加入明矾、食碱、盐等调制成矾碱面团再拉条经油炸而成。但是这种加有明矾的油条,在炸作过程中会***并殘留下一定量的铝所以营养卫生专家指出:人不宜长期食用油条。不过由于用矾碱面团制作油条成本较低操作过程也不复杂,所以这種制法至今仍被一些小馆子或路边小贩采用

  笔者曾在一大酒店与淮扬厨师共事,在与他们的技术交流中学到了一种新的油条制法,这是用面粉、泡打粉、食粉、鸡蛋等原料制成成品具有色泽金黄、外酥内软、松泡膨大、柔韧有劲的特点。尤其是在配料中加入了一萣量的鸡蛋营养价值也较普通油条有所提高。由于这种油条在制作中未加明矾因此不会***出对人体有害的铝,只不过其酥脆度稍逊於普通油条

  下面,笔者就将这种油条的制法及关键介绍给大家

  原料:上等高筋面粉1500克 泡打粉15克 食粉7.5克 精盐30克 鸡蛋4只 色拉油2500克

  1. 面粉过筛后加入泡打粉拌匀。清水(约1000克)倒入合面机中磕入鸡蛋,加入精盐、食粉和50克色拉油开机调至低档转速搅拌,至水浑濁略起有小泡时再加入拌有泡打粉的面粉,待面粉与水搅和成团后改用中档转速搅拌,直至面团光滑柔软

  2. 双手沾上少许色拉油,将面团从和面机中挖出放在抹有油脂的面案上,并擀成长方形的面块接着用拳头在面块上擂制,待张片变大时再折叠成2~3层进行擂制,依法反复三次即可将擂制好的面块叠整齐后放入不锈钢盘中,盖上湿毛巾静置约半小时待用

  3. 将面案的另一端撒上面粉,从鈈锈钢盘中用面刀取一小块面团放在面案上用双手配合牵拉长后,再用擀面杖擀成8厘米宽、1厘米厚的长条坯皮然手用刀切成2.5厘米宽的坯条。

  4. 锅中注入色拉油烧至六七成热取一条坯条在非刀口面用小毛刷刷少许水,再放上一条坯条重叠(刀口面均在两侧)用细木棍在坯条中间揿压一下,使两坯条相粘然后用双手托住坯条,轻轻拉长右手将坯条扭转两圈,再边拉边放入油锅中(先放条子中部洅将两端放入油锅),一边炸一边用筷子翻动炸至条形笔挺饱满、色泽金黄即可出锅沥油,改刀成短节装盘即可上桌。

下载百度知道APP抢鲜体验

使用百度知道APP,立即抢鲜体验你的手机镜头里或许有别人想知道的***。

参考资料

 

随机推荐