讲到java的队列时,讲到java中的CAS操作
回顾下java中的cas,主要采用compareAndSet方法,如AtomicReference中所使用的:
AtomicRefrence.java
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
unsafe.java
/**
* Atomically update Java variable to x if it is currently
* holding expected.
* @return true if successful
*/
public final native boolean compareAndSwapObject(Object o, long offset,
Object expected,
Object x);
compareAndSwapObject是一个本地方法,调用的c++的实现
// Unsafe.h
virtual jboolean compareAndSwapObject(::java::lang::Object *, jlong, ::java::lang::Object *, ::java::lang::Object *);
// natUnsafe.cc
static inline bool
compareAndSwap (volatile jobject *addr, jobject old, jobject new_val)
{
jboolean result = false;
spinlock lock;
// 如果字段的地址与期望的地址相等则将字段的地址更新
if ((result = (*addr == old)))
*addr = new_val;
return result;
}
// natUnsafe.cc
jboolean
sun::misc::Unsafe::compareAndSwapObject (jobject obj, jlong offset,
jobject expect, jobject update)
{
// 获取字段地址并转换为字符串
jobject *addr = (jobject*)((char *) obj + offset);
// 调用 compareAndSwap 方法进行比较
return compareAndSwap (addr, expect, update);
}
这段代码主要做的是:
1、通过对象的首地址跟字段在对象内的偏移量来获取字段的地址
2、判断字段地址是否与我们期望的地址相同,如果相同即更新新的地址
在java中,地址相同说明两个对象相同。
注意到c++源码中使用的 spinlock,其实现:
// Use a spinlock for multi-word accesses
class spinlock
{
static volatile obj_addr_t lock;
public:
spinlock ()
{
while (! compare_and_swap (&lock, 0, 1))
_Jv_ThreadYield ();
}
~spinlock ()
{
release_set (&lock, 0);
}
};
// This is a single lock that is used for all synchronized accesses if
// the compiler can't generate inline compare-and-swap operations. In
// most cases it'll never be used, but the i386 needs it for 64-bit
// locked accesses and so does PPC32. It's worth building libgcj with
// target=i486 (or above) to get the inlines.
volatile obj_addr_t spinlock::lock;
volatile关键字让编译器不进行优化,从而lock值每次都从内存中读取。
通过前半文的了解,我们知道c 提供的函数 compare_and_swap
bool compare_and_swap ( int *memory_location, int expected_value, int new_value)
{
if (*memory_location == expected_value)
{
*memory_location = new_value;
return true;
}
return false;
}
(1)GGC对CAS支持
GCC4.1+版本中支持CAS原子操作。
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...);
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...);
(2)Windows对CAS支持
Windows中使用Windows API支持CAS。
LONG InterlockedCompareExchange(
LONG volatile *Destination,
LONG ExChange,
LONG Comperand
);
(3)C11对CAS支持
C11 STL中atomic函数支持CAS并可以跨平台。
template< class T >
bool atomic_compare_exchange_weak( std::atomic* obj,T* expected, T desired );
template< class T >
bool atomic_compare_exchange_weak( volatile std::atomic* obj,T* expected, T desired );
atomic_compare_exchange_weak当存储值时会发生失败情况,且返回false,所以通常采用while直至比对交换成功,相比使用aotmic_compare_exchange_strong会有更高的性能,而使用while也即是通常所说的自旋效果。
1、生产级队列ConcurrencyQueue
这是我在生产中使用的,因为消费者可以采用wait方式监听队列消息进行消费,所以蛮方便的。
2、boost方案:
boost提供了三种无锁方案,分别适用不同使用场景。
boost::lockfree::queue是支持多个生产者和多个消费者线程的无锁队列。
boost::lockfree::stack是支持多个生产者和多个消费者线程的无锁栈。
boost::lockfree::spsc_queue是仅支持单个生产者和单个消费者线程的无锁队列,比boost::lockfree::queue性能更好。
Boost无锁数据结构的API通过轻量级原子锁实现lock-free,不是真正意义的无锁。
Boost提供的queue可以设置初始容量,添加新元素时如果容量不够,则总容量自动增长;但对于无锁数据结构,添加新元素时如果容量不够,总容量不会自动增长。
因为boost太大了,所以生产比较少用
当我们能把一样东西做出来,说明我们才是真正的了解,所以多造轮子对自己有帮助。
我们采用数组的线性空间来实现环形接口,当数据到达尾部时将其转回到0的位置重写入。
环形结构的容量位置从数组q[0] 到 q[max - 1]。
head表示队列头,tail表示队列尾,当(tail + 1) % max 即表示队列已满
%max 取余 可以 通过位运算 head & (max - 1),需要保证max是2的幂次方
单消费者,单生产者
#pragma once
template<typename T>
class RingBuffer {
private:
//队列大小
unsigned int _size;
//队列头部索引
int _front;
//队列尾部索引
int _tail;
//数据缓冲区
T* _data;
public:
RingBuffer(unsigned int size) :_size(size),_front(0), _tail(0) {
_data = new T[size]