JDK Unsafe类的使用与CAS原子特性

JDK Unsafe类的使用与CAS原子特性

  1. Java.util.concurrent.atomic包,其中包含了大量使用到Unsafe这个类
  2. Java不能直接访问操作系统的底层,而是通过本地方法来访问。

Unsafe类提供了硬件级别的原子操作,主要提供了以下功能

  • 内存操作
  • 字段的定位和修改
  • 挂起和恢复
  • CAS操作(乐观锁)

内存操作

  • 类提供的3个本地方法allocateMemory、reallocteMemory、freeMemory分别用于分配内存、扩充内存和释放内存
//和C语言的3个方法对应

/**分配内存**/
public native long allocateMemory(long 1);

/**扩充内存**/
public native long reallocateMemory(long 1,long l1);

/**释放内存**/
public native void freeMemory(long 1);

字段的定位和修改

  • 可以定位对象字段的内存位置,也可以修改对象的字段值,即使它是私有的

挂起和恢复

  • 将一个线程鼓起是通过park实现的,调用park后,线程会一直阻塞直到超时或者中断等条件的出现
  • unpark可以终止一个挂起的线程,使其恢复正常
  • 整个并发框架中对于线程的挂起操作被封装在LockSupport类中,LockSupport类中有各种版本的pack方法,但是最终都调用了Unsafe.park()方法

CAS操作(乐观锁)

  • CAS(Compare And Swap)比价并交换
  • CAS操作包含三个操作数    内存位置(V)、预期位置(A)、新值(B)
  • 如果内存位置和预期的原值相匹配,那么处理器就会自动将该位置更新为新值。否则,处理器不做任何操作。无论那种情况,它都会在CAS指令之前返回该位置的值
  • 简单讲就是,我认为V应该包含A值,如果复合预期,就将B放到这个位置,否则,不要改变该位置,只告诉我这个位置现在的值就可以
  • Java并发包(java.util.concurrent)中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作,在Unsafe中是通过compareAndSwapXXX方法实现的。

底层方法如下

/*
*比较obj的offset处内存位置中的值和期望的值,如果相同则更新,此更新是不可中断的

*@Param obj需要更新的操作
*@Param offset obj中整型的field偏移量
*@Param expect 希望field中存在的值
*@Param update 如果期望值except与field当前值相同,设置field这个值为新值
*@return 如果field的值将被改变则返回true
*/
public native boolean compareAndSwapInt(Object obj,long offset,int expect,int update)

CountDownLatch

  • CountDownLatch:用于监听某些初始化操作,并且将线程进行阻塞,等初始化执行完毕之后,通知主线程继续工作执行

CyclicBarrier

  • CyclicBarrier:栅栏的概念,多线程的进行阻塞,等待某一个临界值条件满足后,同时执行
  • 比如:将每一个线程比作一个跑步运动员,只有所有的运动员都准备好,才能一起赛跑,只要一个人没有准备好,大家都等等待

Future与Caller回调

  • Future模式:这种模式主要是利用空间换取时间的概念,也就是异步执行(需要开启一个新的线程)
  • Future模式非常适合在处理耗时很长的业务逻辑时进行使用,可以有效的减少系统的响应时间,提高系统的吞吐量

利用设计模式模拟Future

  • Future模式有点类似于商品订单
  • 比如网购的时候,挑选商品,提交订单,付款即可。当订单处理完成之后,在家里等待商品送货上门即可。或者形象的说,当我们发送Ajax请求的时候,页面是异步的进行后台处理,用户无需一直等待请求的结果,可以继续浏览或者操作其他内容

Exchanger多线程间数据交换

  • Exchanger用于进行线程间的数据交换,它提供了一个同步点,在这个同步点,两个线程可以交换彼此的数据
  • 两个线程通过exchange方法交换数据,如果一个线程先执行excange方法,它会一直等待第二个线程也执行exchange方法
  • 当两个线程都达到同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方
  • 使用的场景:1,遗传算法:遗传算法里需要选两个人作为交换对象,这时会交换两人的数据,并使用交叉规则得出两个人的交换结果;2,文字校对:A和B同时录入数据,然后对A和B进行比较,看是否录入一致,保证数据录入的正确性;

ForkJoin并行框架

  • Frok/Join框架是Java提供的一个用于并行执行任务的框架,将一个大任务分割成若干个小任务,最终汇总每一个小任务的结果后从而得到大任务的结果
  • ForkJoinTask:使用该框架,需要创建一个ForkJoin任务,它提供在任务中执行fork和join操作的机制。一般情况下,我们不需要直接继承ForkJoinTask类,只需要继承他的子类即可
  • RecursiveAction:用于没有返回结果的任务
  • RecursiveTask:用于有返回结果的任务
  • ForkJoinPool:任务ForkJoinTask需要通过ForkJoinPool来执行

Master-Worker并发组件设计模式

  • Master-Worker模式是常用的并发计算模式,他的核心思想是系统由两类进程协作工作:Master和aworker进程
  • Master进程负责接收和分配任务,Worker进程负责处理子任务。当各个Worker子进程处理完成之后,会将结果返回给Master,由Master做归纳和总结
  • 其好处是将一个大任务分解成若干个小任务,并行执行,从而提高系统的吞吐量
  • Master-Worker并发组件设计模拟

Semaphore信号量与限流策略

  • Semaphore信号量非常适合并发访问限制,用于对系统的访问量进行评估。投入资源太大,资源利用率达不到实际效果,纯粹浪费资源;投入资源太小的话,如果一个高峰值的访问量会压垮系统

Semaphore相关概念

  • PV(page view)网站的总访问量,页面浏览量或者点击量,用户每刷新一次就会记录一次
  • UV(unique Visitor)访问网站的一台电脑客户端为一个访客。一般来讲时间上00:00~24:00之内相同ip的客户端记录
  • QPS(query per second)即每秒查询数,qps很大程度上代表了系统业务上的繁忙程度,每次请求的背后,可能对应着多次磁盘I/O,多次网络请求,多个cpu时间片等。通过qps可以非常直观了解当前系统业务情况,一旦当前qps超过所设定的预警阀值,可以考虑增加机器对于集群的扩容,防止压力太大导致宕机,可以根据前期的压力测试,在结合后期压力测试得到估值,再结合后期综合运维情况,估算出阀值
  • RT(response time)请求的响应时间,这个指标非常关键,直接说明前段用户的体验
  • 当然还涉及cpu、内存、网络、磁盘等情况,更细节的问题很多,如select、update、delete/ps等数据库层面的统计。
  • 容量评估:一般来说通过开发、运维、测试、以及业务等相关人员,综合出系统的一系列阀值,然后我们根据关键阀值如qps、rt等,对系统进行有效的变更。
  • 一般来讲,我们进行多轮压力测试以后,可以对系统进行峰值评估,采用所谓的80/20原则,即80%的访问请求将在20%的时间内达到。这样我们可以根据系统对应的PV计算出峰值qps。
  • 峰值qps= (总PV × 80%)/ (60 × 60 × 24 × 20%)
  • 然后在将总的峰值qps除以单台机器所能承受的最高的qps值,就是所需要机器的数量:机器数 = 总的峰值qps / 压测得出的单机极限qps
  • 当然不排除系统在上线前进行大型促销活动,或者双十一、双十二热点事件、遭受到DDos攻击等情况,系统的开发和运维人员急需要了解当前系统运行的状态和负载情况,一般都会有后台系统去维护。    
发布了53 篇原创文章 · 获赞 1 · 访问量 1161

猜你喜欢

转载自blog.csdn.net/CHYabc123456hh/article/details/104454643