1.画面设置cas是码下什么意思?
2.ssoåshiroçåºå«
3.从HotSpot源码,深度解读 park 和 unpark
4.深入学习CAS底层原理
5.Linux基础组件之无锁消息队列ypipe/yqueue详解
画面设置cas是码下什么意思?
CAS是Central Authentication Service的缩写,即集中式认证服务。码下它是码下一种用于Web应用程序的单点登录协议。CAS协议通过认证中心(服务器)来给多个服务提供认证服务,码下用户一次登录认证以后,码下源码抓捕便可以访问被授权的码下多个服务。CAS协议是码下一种开放源代码的协议,被广泛应用于大型企业和组织的码下身份认证系统中。
CAS需要先部署一个认证服务器和多个应用程序服务器,码下然后在这些服务器之间建立信任关系。码下用户首次登录时,码下应该重定向到认证服务器,码下输入用户名和密码进行认证,码下并且一旦通过认证,码下用户将被重定向回要访问的应用程序服务器。以后的每次访问都无需再次认证。认证服务器和应用程序服务器之间使用安全令牌和Session来保障安全性。
CAS的优点在于提供可靠的身份验证,减少了用户访问多个Web应用程序时的不必要的登录操作,避免了重复输入用户名和密码等问题。它广泛应用于大型企业和组织的身份认证系统中,例如教育机构、银行、harkchat源码保险公司、医院等。CAS的使用可以帮助企业或组织节省时间和成本,减少安全漏洞,提高用户体验并提高整个系统的安全性。
ssoåshiroçåºå«
SSOæ¯åç¹ç»å½ï¼Shiroæ¯æé管çï¼ä½æ¯é åCASå¯ä»¥å®ç°SSOæ¨èä¸å¥å®æ´çShiro Demoï¼å è´¹çã
Shiroä»ç»ææ¡£ï¼/shiroDemoå·²ç»é¨ç½²å°çº¿ä¸ï¼ShiroDemo: å¦æå¯ç é误ï¼è¯·ç¨sojsonãPSï¼ä½ å¯ä»¥æ³¨åèªå·±çå¸å·ï¼ç¶åç¨ç®¡çåèµæéç»ä½ èªå·±çå¸å·ï¼ä½æ¯ï¼æ¯åéä¼ææ°æ®åå§åä¸æ¬¡ã建议èªå·±ä¸è½½æºç ï¼è®©Demoè·èµ·æ¥ï¼ç¶åè·çæ´å¿«ã
从HotSpot源码,深度解读 park 和 unpark
我最近建立了一个在线自习室(App:番茄ToDO)用于相互监督学习,感兴趣的小伙伴可以加入。自习室加入码:D5A7A
Java并发包下的类大多基于AQS(AbstractQueuedSynchronizer)框架实现,而AQS线程安全的实现依赖于两个关键类:Unsafe和LockSupport。
其中,Unsafe主要提供CAS操作(关于CAS,在文章《读懂AtomicInteger源码(多线程专题)》中讲解过),LockSupport主要提供park/unpark操作。实际上,park/unpark操作的最终调用还是基于Unsafe类,因此Unsafe类才是核心。
Unsafe类的实现是由native关键字说明的,这意味着这个方法是原生函数,是用C/C++语言实现的,并被编译成了DLL,由Java去调用。
park函数的ludo源码作用是将当前调用线程阻塞,而unpark函数则是唤醒指定线程。
park是等待一个许可,unpark是为某线程提供一个许可。如果线程A调用park,除非另一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。每次调用一次park,需要有一个unpark来解锁。
并且,unpark可以先于park调用,但不管unpark先调用多少次,都只提供一个许可,不可叠加。只需要一次park来消费掉unpark带来的许可,再次调用会阻塞。
在Linux系统下,park和unpark是通过Posix线程库pthread中的mutex(互斥量)和condition(条件变量)来实现的。
简单来说,mutex和condition保护了一个叫_counter的信号量。当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。ostream源码当_counter=0时线程阻塞,当_counter>0时直接设为0并返回。
每个Java线程都有一个Parker实例,Parker类的部分源码如下:
由源码可知,Parker类继承于PlatformParker,实际上是用Posix的mutex和condition来实现的。Parker类里的_counter字段,就是用来记录park和unpark是否需要阻塞的标识。
具体的执行逻辑已经用注释标记在代码中,简要来说,就是检查_counter是不是大于0,如果是,则把_counter设置为0,返回。如果等于零,继续执行,阻塞等待。
unpark直接设置_counter为1,再unlock mutex返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程。源码如下:
(如果不会下载JVM源码可以后台回复“jdk”,获得下载压缩包)
深入学习CAS底层原理
什么是tbox源码CAS
CAS是Compare-And-Swap的缩写,意思为比较并交换。以AtomicInteger为例,其提供了compareAndSet(intexpect,intupdate)方法,expect为期望值(被修改的值在主内存中的期望值),update为修改后的值。compareAndSet方法返回值类型为布尔类型,修改成功则返回true,修改失败返回false。
举个compareAndSet方法的例子:
publicclassAtomticIntegerTest{ publicstaticvoidmain(String[]args){ AtomicIntegeratomicInteger=newAtomicInteger(0);booleanresult=atomicInteger.compareAndSet(0,1);System.out.println(result);System.out.println(atomicInteger.get());}}上面例子中,通过AtomicInteger(intinitialValue)构造方法指定了AtomicInteger类成员变量value的初始值为0:
publicclassAtomicIntegerextendsNumberimplementsjava.io.Serializable{ ......privatevolatileintvalue;/***CreatesanewAtomicIntegerwiththegiveninitialvalue.**@paraminitialValuetheinitialvalue*/publicAtomicInteger(intinitialValue){ value=initialValue;}......}接着执行compareAndSet方法,main线程从主内存中拷贝了value的副本到工作线程,值为0,并将这个值修改为1。如果此时主内存中value的值还是为0的话(言外之意就是没有被其他线程修改过),则将修改后的副本值刷回主内存更新value的值。所以上面的例子运行结果应该是true和1:
将上面的例子修改为:
publicclassAtomticIntegerTest{ publicstaticvoidmain(String[]args){ AtomicIntegeratomicInteger=newAtomicInteger(0);booleanfirstResult=atomicInteger.compareAndSet(0,1);booleansecondResult=atomicInteger.compareAndSet(0,1);System.out.println(firstResult);System.out.println(secondResult);System.out.println(atomicInteger.get());}}上面例子中,main线程第二次调用compareAndSet方法的时候,value的值已经被修改为1了,不符合其expect的值,所以修改将失败。上面例子输出如下:
CAS底层原理查看compareAndSet方法源码:
/***Atomicallysetsthevalueto{ @codenewValue}*ifthecurrentvalue{ @code==expectedValue},*withmemoryeffectsasspecifiedby{ @linkVarHandle#compareAndSet}.**@paramexpectedValuetheexpectedvalue*@paramnewValuethenewvalue*@return{ @codetrue}ifsuccessful.Falsereturnindicatesthat*theactualvaluewasnotequaltotheexpectedvalue.*/publicfinalbooleancompareAndSet(intexpectedValue,intnewValue){ returnU.compareAndSetInt(this,VALUE,expectedValue,newValue);}该方法通过调用unsafe类的compareAndSwapInt方法实现相关功能。compareAndSwapInt方法包含四个参数:
this,当前对象;
valueOffset,value成员变量的内存偏移量(也就是内存地址):
privatestaticfinallongvalueOffset;static{ try{ valueOffset=unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));}catch(Exceptionex){ thrownewError(ex);}}expect,期待值;
update,更新值。
所以这个方法的含义为:获取当前对象value成员变量在主内存中的值,和传入的期待值相比,如果相等则说明这个值没有被别的线程修改过,然后将其修改为更新值。
那么unsafe又是什么?它的compareAndSwapInt方法是原子性的么?查看该方法的源码:
/***AtomicallyupdatesJavavariableto{ @codex}ifitiscurrently*holding{ @codeexpected}.**<p>Thisoperationhasmemorysemanticsofa{ @codevolatile}read*andwrite.CorrespondstoCatomic_compare_exchange_strong.**@return{ @codetrue}ifsuccessful*/@HotSpotIntrinsicCandidatepublicfinalnativebooleancompareAndSetInt(Objecto,longoffset,intexpected,intx);该方法并没有具体Java代码实现,方法通过native关键字修饰。由于Java方法无法直接访问底层系统,Unsafe类相当于一个后门,可以通过该类的方法直接操作特定内存的数据。Unsafe类存在于sun.msic包中,JVM会帮我们实现出相应的汇编指令。Unsafe类中的CAS方法是一条CPU并发原语,由若干条指令组成,用于完成某个功能的一个过程。原语的执行必须是连续的,在执行过程中不允许被中断,不会存在数据不一致的问题。
getAndIncrement方法剖析了解了CAS原理后,我们回头看下AtomicInteger的getAndIncrement方法源码:
/***Atomicallyincrementsthecurrentvalue,*withmemoryeffectsasspecifiedby{ @linkVarHandle#getAndAdd}.**<p>Equivalentto{ @codegetAndAdd(1)}.**@returnthepreviousvalue*/publicfinalintgetAndIncrement(){ returnU.getAndAddInt(this,VALUE,1);}该方法通过调用unsafe类的getAndAddInt方法实现相关功能。继续查看getAndAddInt方法的源码:
/***Atomicallyaddsthegivenvaluetothecurrentvalueofafield*orarrayelementwithinthegivenobject{ @codeo}*atthegiven{ @codeoffset}.**@paramoobject/arraytoupdatethefield/elementin*@paramoffsetfield/elementoffset*@paramdeltathevaluetoadd*@returnthepreviousvalue*@since1.8*/@HotSpotIntrinsicCandidatepublicfinalintgetAndAddInt(Objecto,longoffset,intdelta){ intv;do{ v=getIntVolatile(o,offset);}while(!weakCompareAndSetInt(o,offset,v,v+delta));returnv;}结合源码,我们便可以很直观地看出为什么AtomicInteger的getAndIncrement方法是线程安全的了:
o是AtomicInteger对象本身;offset是AtomicInteger对象的成员变量value的内存地址;delta是需要变更的数量;v是通过unsafe的getIntVolatile方法获得AtomicInteger对象的成员变量value在主内存中的值。dowhile循环中的逻辑为:用当前对象的值和var5比较,如果相同,说明该值没有被别的线程修改过,更新为v+delta,并返回true(CAS);否则继续获取值并比较,直到更新完成。
CAS的缺点CAS并不是完美的,其存在以下这些缺点:
如果刚好while里的CAS操作一直不成功,那么对CPU的开销大;
只能确保一个共享变量的原子操作;
存在ABA问题。
CAS实现的一个重要前提是需要取出某一时刻的数据并在当下时刻比较交换,这之间的时间差会导致数据的变化。比如:thread1线程从主内存中取出了变量a的值为A,thread2页从主内存中取出了变量a的值为A。由于线程调度的不确定性,这时候thread1可能被短暂挂起了,thread2进行了一些操作将值修改为了B,然后又进行了一些操作将值修改回了A,这时候当thread1重新获取CPU时间片重新执行CAS操作时,会发现变量a在主内存中的值仍然是A,所以CAS操作成功。
解决ABA问题那么如何解决CAS的ABA问题呢?由上面的阐述课件,光通过判断值是否相等并不能确保在一定时间差内值没有变更过,所以我们需要一个额外的指标来辅助判断,类似于时间戳,版本号等。
JUC为我们提供了一个AtomicStampedReference类,通过查看它的构造方法就可以看出,除了指定初始值外,还需指定一个版本号(戳):
/***Createsanew{ @codeAtomicStampedReference}withthegiven*initialvalues.**@paraminitialReftheinitialreference*@paraminitialStamptheinitialstamp*/publicAtomicStampedReference(VinitialRef,intinitialStamp){ pair=Pair.of(initialRef,initialStamp);}我们就用这个类来解决ABA问题,首先模拟一个ABA问题场景:
publicclassAtomticIntegerTest{ publicstaticvoidmain(String[]args){ AtomicReference<String>atomicReference=newAtomicReference<>("A");newThread(()->{ //模拟一次ABA操作atomicReference.compareAndSet("A","B");atomicReference.compareAndSet("B","A");System.out.println(Thread.currentThread().getName()+"线程完成了一次ABA操作");},"thread1").start();newThread(()->{ //让thread2先睡眠2秒钟,确保thread1的ABA操作完成try{ TimeUnit.SECONDS.sleep(2);}catch(InterruptedExceptione){ e.printStackTrace();}booleanresult=atomicReference.compareAndSet("A","B");if(result){ System.out.println(Thread.currentThread().getName()+"线程修改值成功,当前值为:"+atomicReference.get());}},"thread2").start();}}运行程序,输出如下:
使用AtomicStampedReference解决ABA问题:
publicclassAtomicIntegerextendsNumberimplementsjava.io.Serializable{ ......privatevolatileintvalue;/***CreatesanewAtomicIntegerwiththegiveninitialvalue.**@paraminitialValuetheinitialvalue*/publicAtomicInteger(intinitialValue){ value=initialValue;}......}0程序输出如下:
Linux基础组件之无锁消息队列ypipe/yqueue详解
CAS定义
比较并交换(compare and swap, CAS),在多线程编程中用于实现不被打断的数据交换,避免数据不一致问题。该操作通过比较内存值与指定数据,当数值相同则替换内存数据。
为什么需要无锁队列
锁引起的问题:cache损坏/失效、同步机制上的争抢、动态内存分配。
有锁导致线程切换引发cache损坏
大量线程切换导致cache数据失效,处理器与主存之间数据传输效率下降,影响性能。
在同步机制上的争抢队列
阻塞队列导致任务暂停或睡眠,大量时间浪费在获取互斥锁,而非处理数据,引发严重争用。
动态内存分配
多线程中动态分配内存导致互斥,线程频繁分配内存影响应用性能。
无锁队列的实现
无锁队列由ypipe_t和yqueue_t类构成,适用于一读一写场景。通过chunk模式批量分配结点,减少动态内存分配的互斥问题。批量分配大小根据业务场景调整,通常设置较大较为安全。利用spare_chunk存储未释放的chunk,降低频繁分配释放。预写机制减少CAS调用。巧妙的唤醒机制,读端等待无数据时进入等待状态,写端根据返回值判断队列是否为空以唤醒读端。
无锁队列使用
yqueue.write(count,false)用于写入元素并标记完成状态,yqueue.flush()使读端可见更新后数据。yqueue.read(&value)读取元素,返回true表示读到元素,返回false表示队列为空。
ypipe_t使用
write(val, false)更新写入位置,flush()刷新数据到管道,read()读取数据并更新可读位置。
yqueue_t构造函数
初始化队列,end_chunk总是指向最后分配的chunk,back_chunk仅在有元素插入时指向对应的chunk。
front()和back()函数
返回队列头和尾的可读写元素位置。
push()和pop()函数
push()更新写入位置,pop()更新读取位置并检测释放chunk,保持数据流。
源码分析
yqueue_t内部使用chunk批量分配,减少内存操作,spare_chunk存储释放的chunk以供再次使用。ypipe_t构建单写单读无锁队列,通过CAS操作控制读写位置,实现高效数据交换。
ypipe_t / yqueue_t无锁队列利用chunk机制避免频繁内存动态分配,提升性能。通过局部性原理复用回收的chunk,减少资源消耗。flush()检测队列状态通知唤醒,优化数据交换过程。