对于单生产者-单消费者的情况,可以实现无锁环形缓冲区,常见的实现如下:
public class RingBuffer {
private int mHead;
private int mTail;
private char[] mBuffer;
public RingBuffer(int size) {
if (size < 2) {
throw new Exception("size should be greater than 2");
}
mBuffer = new char[size];
mHead = 0;
mTail = 0;
}
public int length() {
return (mTail + mBuffer.length - mHead) % mBuffer.length;
}
public boolean isEmpty() {
return mHead == mTail;
}
public boolean isFull() {
return mHead == ((mTail + 1) % mBuffer.length);
}
public char read() {
if (isEmpty()) {
throw new Exception("buffer is already empty");
}
char ch = mBuffer[mHead];
mHead = (mHead + 1) % mBuffer.length;
return ch;
}
public void write(char ch) {
if (isFull()) {
throw new Exception("buffer is already full");
}
mBuffer[mTail] = ch;
mTail = (mTail + 1) % mBuffer.length;
}
}
mHead
表明下一个可读取的位置,mTail
表明下一个可写入的位置,head和tail重合时,表明buffer是空的;为了区分empty和full,强制要求tail和head之间至少要空一个元素,也就是说对于size为N的数组实现的环形buffer,最多只能存放N - 1个元素。如果不做这样的强制要求,则无法区分empty和full。因此,数组的size要大于2才有效。read和write函数各自只修改了head和tail, 所以即使运行在不同的线程,也能保证数据的一致性。
对于size为N的数组实现的环形buffer,如果想实现最多可以存放N个元素,则必须引入length变量,而且无法做到lock-free。也就是说,只借助head和tail, 无法区分empty和full, 所以要借助empty和full时head和tail的间隔不同来帮忙区分。
public class RingBuffer {
private int mHead;
private int mTail;
private int mLength;
private char[] mBuffer;
public RingBuffer(int size) {
if (size < 1) {
throw new Exception("size should be greater than 0");
}
mBuffer = new char[size];
mHead = 0;
mTail = 0;
}
public int length() {
return mLength;
}
public boolean isEmpty() {
return length() == 0;
}
public boolean isFull() {
return length() == mBuffer.length;
}
public char read() {
if (isEmpty()) {
throw new Exception("buffer is already empty");
}
char ch = mBuffer[mHead];
mHead = (mHead + 1) % mBuffer.length;
mLength--;
return ch;
}
public void write(char ch) {
if (isFull()) {
throw new Exception("buffer is already full");
}
mBuffer[mTail] = ch;
mTail = (mTail + 1) % mBuffer.length;
mLength++;
}
}