概述
硬件事务内存有能力提供多线程并发访问数据结构的需要,并可以通过缓存一致性协议
来避免数据冲突的发生。HTM(硬件事务内存)旨在为你提供更细粒度锁的扩展性以及简单的粗粒度
加锁操作,并且性能与无锁操作不相上下。如果你使用支持它的虚拟机版本的话,通过简单的所控制
或者类库不需要多大改动就可以做到在具有大量处理核的环境中正常使用的效果。
然而如果要为C或者C++类型的程序添加这种支持那也是很有意义的事情,对java虚拟机生成的本地代码添加
也不会对生成的字节码信息有所改动。
总之,有了它就可以在并发的时候具备了执行并发的能力,甚至对于并发写操作或者即使发生异常的情况下,只要还是在执行同一个代码块处理器都能游刃有余的解决。
什么是硬件事务内存,使用它的资源消耗如何?
硬件事务内存虽然已经被提出了有些年了,但是直到如今它的使用还不是主流。通过Intel在它的第四代i3/i5/i7(Haswell)
和E3-1200 V3(包括4核的,一个异步传输socket接口)家族的处理器上提供了对硬件事务内存的支持。在今年的晚些时候,或者
下年我们会看到会有越来越多的CPU会提供对它的支持,AFAIK、AMD很快也会计划增加对它的支持。
顺便说一下Azul's Vega系统对此的支持已经有了差不多十个年头了,希望Azul成为率先在jvm上增加支持做出好的表率来。
说不定你的机器已经是能支持它的,很多新型的笔记本一般都会使用支持Haswell的处理器来优化电池性能。
它的实现机制是什么样的?
在java中,轻易是不会使用synchronized关键字的。为了代码的简洁,这样的加锁不仅比做任何操作都要锁定整个对象或者map
的Hashtable更粗糙,更比不上使用更细粒度锁机制的ConcurrentHashMap了。由于锁机制使用的易错性。硬件事务内存就是为了让设计者可以不必太关注锁的使用却能获得很好的使用效果。这使得可以不用花心思优化就能做出很好的锁控制效果。
例如
private final Map<String, PooledObject> map = new HashMap<>(); public synchronized PooledObject acquireObject(String key) { PooledObject object = map.get(key); if (object == null) map.put(key, object = new PooledObject()); return object; }
你可能有以下的场景要去处理
1.只希望对map进行读取
2.用不同的key来更新不同的value
3.很少会出现两个线程同时去更新同一key的情况
那么你所需要的又是什么呢
1.多线程并发执行
2.加锁和不加锁的情况下代码改动不大
3.不用修改代码,Cpu或者JVM会自动对其做优化
public PooledObject acquireObject(String key) { int code; do { xbegin(); PooledObjectobject = map.get(key); if (object == null) map.put(key, object = new PooledObject()); return map; } while((code = xend()) == RETRYABLE); if (code != DONE) { // take corrective action such as // obtain a normal lock and repeat } }
如果没有HTM,即使在大多数是读的情况下,也必须通过同步加锁获取锁并且强制对资源的线性访问。
使用HTM的情况下伪代码可以如下编写
public PooledObject acquireObject(String key) { int code; do { xbegin(); PooledObjectobject = map.get(key); if (object == null) map.put(key, object = new PooledObject()); return map; } while((code = xend()) == RETRYABLE); if (code != DONE) { // take corrective action such as // obtain a normal lock and repeat } }
XEND指令会检查当前的读取集合和缓存中的写集合,并判断是否它们的缓存行已经被其他CPU或者线程过,
如果没有被修改过,指令会提交修改的内容。否则任何的修改都会被丢弃然后循环执行此操作。
注意:事务回滚会还原修改的内容并且销毁对象以防会有副作用。如果担心数据回滚造成的影响,则应该调用XABORT指令来丢弃事务并且利用代码进行恢复。
因为CAS操作在64位系统上是受限制的,那么对于这些事务限制又有哪些啊?
首先是可以在L1缓存层上的行数是有限的。最多只有32KB的大小。如果是支持超线程的,那么可能只16KB了。
另外因为L1缓存层是八通道的,最坏情况下9个缓存行被映射到其上的话就会导致事务失败,然而即使这样也比64位上的CAS或者128位上的2CAS的灵活性要好的多。