JDK Unsafe类的使用与CAS原子特性
- Java.util.concurrent.atomic包,其中包含了大量使用到Unsafe这个类
- 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攻击等情况,系统的开发和运维人员急需要了解当前系统运行的状态和负载情况,一般都会有后台系统去维护。