堆外并发计数器

来源:互联网 发布:js canvas 动画库 编辑:程序博客网 时间:2024/05/22 17:43

堆外并发计数器 

分享到:2


本文由 ImportNew - 人晓 翻译自 javacodegeeks。欢迎加入翻译小组。转载请参见文章末尾的要求。

并发计数器几乎每个系统都有,它通常用来收集数据、实现线程同步。Java对于基于堆的计数器有很好的支持。

在某些情况下,需要建立多个进程共享的计数器。

如何建立进程间的计数器?

数据库

数据库是我们最容易想到的方法,数据库索引是一个可以被多进程共享的计数器。所有的并发都由数据库处理。对于初学者来说这是一个不错的选择,但是我们知道数据库会降低网络、锁等一些方面的性能。只有Larry Elision(译者注:Oracle的CEO)会对此感到高兴,你不会的。

服务器

你可以开发服务器或中间件提供这类服务,不过这种方法仍然会存在网络、序列化/反序列化方面的开销。

内存映射文件

你可以通过使用内存映射文件解决这一问题。我在听了PeterLawrey关于“Java中共享内存的进程间的线程安全”演讲之后想到了这个方法。

多进程计数器中遇到的困难

数据可视化

进程对数据的改变对于其他进程是可见的。这个问题可以通过使用内存映射文件解决,操作系统可以保证这一功能的实现,Java的内存模式也能支持这种方式。

线程安全

由于计数器有多个写入者,这使得线程安全成了一个很大的问题。比较与交换(Compare-and-swap,CAS)可以用来解决多个写入者的问题。但是,是否可以使用CAS实现堆外操作呢?答案是肯定的,欢迎使用Unsafe类。通过使用内存映射与Unsafe类结合可以使用CAS实现堆外操作。

在这篇博客中我将向大家分享我使用CAS实现内存映射的经验。

到底如何实现的?

怎样或得内存地址?

MappedByteBuffer使用DirectByteBuffer,其实是堆外内存。所以,我们可以获得内存的虚拟地址,并使用Unsafe类实现CAS操作。请看下面的代码:

1
2
3
4
FileChannel fc = newRandomAccessFile(fileName, "rw").getChannel();
// Map 8 bytes for long value.
mem = fc.map(FileChannel.MapMode.READ_WRITE, 0,8);
startAddress = ((DirectBuffer) mem).address();

上面的代码创建了八字节的内存映射文件,同时获得了它的虚拟地址。这个地址可以用来读写内存映射文件中的内容。

如何实现线程安全的读写?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
publicboolean increment() {
        longorignalValue = readVolatile(startAddress);
        longvalue = convert(orignalValue);
        returnUnsafeUtils.unsafe.compareAndSwapLong(null,
                startAddress,orignalValue, convert(value + 1));
    }
 
    publiclong get() {
        longorignalValue = readVolatile(startAddress);
        returnconvert(orignalValue);
    }
 
    // Only unaligned is implemented
    privatestatic long readVolatile(longposition) {
        if(UnsafeUtils.unaligned()) {
            returnUnsafeUtils.unsafe.getLongVolatile(null, position);
        }
        thrownew UnsupportedOperationException();
    }
 
    privatestatic long convert(longa) {
        if(UnsafeUtils.unaligned()) {
            return(UnsafeUtils.nativeByteOrder ? a : Long.reverseBytes(a));
        }
        thrownew UnsupportedOperationException();
    }

重点看一下readVolatile和increment函数,readVolatile直接从内存读取内容,increment 使用Unsafe类在MemoryByteBuffer得到的地址上实现CAS操作。

性能

下面是系统的性能参数。每个线程计数器统计一百万次。

计数器的性能在下降,随着线程数量的增加CAS操作的失败次数也开始增加,性能开始下降。这些计数器的性能可以通过资源分割减少资源来提高。我会在下一篇博文中详细介绍。

结论

  • 内存映射文件十分有效,它可以用来开发一系列东西,如堆外集合、IPC、堆外线程同步等;
  • 内存映射文件减少了垃圾回收GC的编程。
    博客中的所有代码可以在github中下载。
0 0
原创粉丝点击