前言
前段时间无聊抄了一下RocketMQ源码,刚刚抄完存储部分,感觉有些并发写的非常好,所以总结下。
1.CountDownLatch的改造,支持重置
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class CountDownLatch2 {
private final Sync sync;
/**
* Constructs a {@code CountDownLatch2} initialized with the given count.
*
* @param count the number of times {@link #countDown} must be invoked before threads can pass through {@link
* #await}
* @throws IllegalArgumentException if {@code count} is negative
*/
public CountDownLatch2(int count) {
if (count < 0)
throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
*
* <p>If the current count is zero then this method returns immediately.
*
* <p>If the current count is greater than zero then the current
* thread becomes disabled for thread scheduling purposes and lies
* dormant until one of two things happen:
* <ul>
* <li>The count reaches zero due to invocations of the
* {@link #countDown} method; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread.
* </ul>
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while waiting,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared.
*
* @throws InterruptedException if the current thread is interrupted while waiting
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {@linkplain Thread#interrupt interrupted},
* or the specified waiting time elapses.
*
* <p>If the current count is zero then this method returns immediately
* with the value {@code true}.
*
* <p>If the current count is greater than zero then the current
* thread becomes disabled for thread scheduling purposes and lies
* dormant until one of three things happen:
* <ul>
* <li>The count reaches zero due to invocations of the
* {@link #countDown} method; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
* <li>The specified waiting time elapses.
* </ul>
*
* <p>If the count reaches zero then the method returns with the
* value {@code true}.
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while waiting,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared.
*
* <p>If the specified waiting time elapses then the value {@code false}
* is returned. If the time is less than or equal to zero, the method
* will not wait at all.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the {@code timeout} argument
* @return {@code true} if the count reached zero and {@code false} if the waiting time elapsed before the count
* reached zero
* @throws InterruptedException if the current thread is interrupted while waiting
*/
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* Decrements the count of the latch, releasing all waiting threads if
* the count reaches zero.
*
* <p>If the current count is greater than zero then it is decremented.
* If the new count is zero then all waiting threads are re-enabled for
* thread scheduling purposes.
*
* <p>If the current count equals zero then nothing happens.
*/
public void countDown() {
sync.releaseShared(1);
}
/**
* Returns the current count.
*
* <p>This method is typically used for debugging and testing purposes.
*
* @return the current count
*/
public long getCount() {
return sync.getCount();
}
public void reset() {
sync.reset();
}
/**
* Returns a string identifying this latch, as well as its state.
* The state, in brackets, includes the String {@code "Count ="}
* followed by the current count.
*
* @return a string identifying this latch, as well as its state
*/
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
/**
* Synchronization control For CountDownLatch2.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
private final int startCount;
Sync(int count) {
this.startCount = count;
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (; ; ) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
protected void reset() {
setState(startCount);
}
}
}
相比自带的CountDownLatch多了一个reset()方法,在Sync中多了一个startCount变量用来保存CountDownLatch构造参数传过来的初始计数,当reset()重新设置计数值。
好处:CountDownLatch本身设计成一个临时的,但是有些场景需要重复使用,有了reset()就避免了重复创建
2.继承线程工厂,重写newThread方法,让每个线程创建出来的都是守护进程
public class ThreadFactoryImpl implements ThreadFactory {
private final AtomicLong threadIndex = new AtomicLong(0);
private final String threadNamePrefix;
private final boolean daemon;
public ThreadFactoryImpl(final String threadNamePrefix) {
this(threadNamePrefix, false);
}
public ThreadFactoryImpl(final String threadNamePrefix, boolean daemon) {
this.threadNamePrefix = threadNamePrefix;
this.daemon = daemon;
}
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, threadNamePrefix + this.threadIndex.incrementAndGet());
thread.setDaemon(daemon);
return thread;
}
}
这里用到的是一个工厂模式,将我们的后台服务进程如检测磁盘的占用率,设置成守护进程。也就是说我们的后台服务就像GC一样,等待最后一个用户进程结束时,随着jvm的结束而结束。
3.交换容器的设计思想
public class GroupCommitService extends Thread {
class GroupCommitRequest {
}
private volatile List<GroupCommitRequest> requestsWrite = new ArrayList<GroupCommitRequest>();//任务提交容器。
private volatile List<GroupCommitRequest> requestsRead = new ArrayList<GroupCommitRequest>(); //任务执行容器
public synchronized void putRequest(final GroupCommitRequest request) {
//客户端提交同步刷盘任务到GroupCommitService线程
synchronized (this.requestsWrite) {
this.requestsWrite.add(request);
}
}
private void swapRequests() {
//写和读的互相交换一下
List<GroupCommitRequest> tmp = this.requestsWrite;
this.requestsWrite = this.requestsRead;
this.requestsRead = tmp;
}
private void doCommit() {
synchronized (this.requestsRead) {//获取
if (!this.requestsRead.isEmpty()) {
for (GroupCommitRequest req : this.requestsRead) {
}
this.requestsRead.clear();
}
}
}
@Override
public void run() {
while (true) { //服务没有被停止
try {
//如果没有待处理的任务,则休息10ms,即每10ms空转一次,
Thread.sleep(10);
//进行提交操作。
this.doCommit();
//交换请求。
this.swapRequests();
} catch (Exception e) {
}
}
}
}
简单写了个demo,核心就是将任务提交和任务执行做分离,避免了锁的冲突,我感觉它是一个读写分离的思想。
4.大量CompletableFuture使用
import java.util.concurrent.CompletableFuture;
public class Test01 {
public static void main(String[] args) {
Test01 test01 = new Test01();
test01.asyncProcessRequest("test");
System.out.println("main结束");
}
public void asyncProcessRequest(String msg){
asyncPutMessageProcessor(msg).thenAcceptAsync((r) -> {
System.out.println("asyncPutMessageProcessor: " + r);
});
}
public CompletableFuture<String>asyncPutMessageProcessor(String msg){
CompletableFuture<String> putMessageResult = asyncPutMessage(msg);
return handlePutMessageResultFuture(putMessageResult);
}
public CompletableFuture<String> asyncPutMessage(String message) {
if (message == null) return CompletableFuture.completedFuture("ERROR");
CompletableFuture<String> putResultFuture = asyncPutCommitLogMessage(message);
putResultFuture.thenAccept((result) -> {
//做处理
System.out.println("asyncPutMessage:"+message);
});
return putResultFuture;
}
public CompletableFuture<String> handlePutMessageResultFuture(CompletableFuture<String> putMessageResult) {
return putMessageResult.thenApply((r) -> handlePutMessageResult(r));
}
public String handlePutMessageResult(String r) {
if (r == "OK" || r == "ERROR") return r;
return "UNKNOWN";
}
public CompletableFuture<String> asyncPutCommitLogMessage(String msg) {
CompletableFuture<String> flushResultFuture = submitFlushRequest(msg);
CompletableFuture<String> replicaResultFuture = submitReplicaRequest(msg);
return flushResultFuture.thenCombine(replicaResultFuture, (flushStatus, replicaStatus) -> {
if (flushStatus != "OK" || replicaStatus != "OK") {
return "ERROR";
}
return "OK";
});
}
//写本地
public CompletableFuture<String> submitFlushRequest(String msg) {
if (msg == null) return CompletableFuture.completedFuture("ERROR");
return putMessage(msg,"Flush");//偷懒写法
}
//写从节点
public CompletableFuture<String> submitReplicaRequest(String msg) {
if (msg == null) return CompletableFuture.completedFuture("ERROR");
return putMessage(msg,"Replica");
}
public CompletableFuture<String> putMessage(String message, String type){
CompletableFuture<String> flushOKFuture = new CompletableFuture<>();
Thread thread = new Thread(() -> {
// 生成一个10至30之间的随机数作为睡眠时间(单位:秒)
long sleepTime = (long) (Math.random() * 21 + 10);
try {
System.out.println("线程将睡眠 " + sleepTime + " 秒");
Thread.sleep(sleepTime * 1000); // 注意转换为毫秒
System.out.println((type == "Flush"? "Flush :":"Replica :") +message);
flushOKFuture.complete("OK");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("线程被中断");
}
// 睡眠结束后执行的操作
System.out.println("线程执行后续操作");
});
// 启动线程
thread.start();
return flushOKFuture;
}
参考同步双写,写了一个小demo。
5.CopyOnWriteArrayList的使用(cow写时复制思想)
private final CopyOnWriteArrayList<MappedFile> mappedFiles = new CopyOnWriteArrayList<MappedFile>();
我硬凑一下,就是感觉这个地方用的比较好,这个是在MappedFileQueue类使用的,每个MappedFile都代表一个文件,MappedFileQueue是一个目录下所有文件的合集。为什么说用的好呢?因为mappedFiles里面的文件很少增加和删除,但是读的情况非常多。以commitlog为例,查找消息就会查询mappedFiles列表。那么增加和删除呢?每个commitlog大小为1G,写满才会创建新的,删除如果不是磁盘占用高的话每天才会清除一次超过三天的文件。所以说是“读多写少”。