主要问题:
声明CountDownLatch时需要指定要等待的线程数;对于无法预知线程数的任务不友好。
应用场景:
无法提前获知需要多少线程的场景;
如:查询es中的数据时,如果数据量很大,无法一次查完,就需要while循环,然后靠lastNum、searchAfter确定下次查询的起始点,分批次查出数据,再丢入线程池中处理;
这样就无法在开始查询前确定需要启动多少线程,也就无法使用CountDownLatch。
解决思路:
借鉴CountDownLatch,每启动一个线程,就把AQS中的state减1,启动完所有线程后,调用await(int count) ,等待的同时把启动的线程数传进锁里,从而实现并发处理;
用法:
/**
* main task
*/
{
int count = 0;
CountDownLatchWithNoCount countDownLatchWithNoCount = new CountDownLatchWithNoCount();
while (/*condition*/) {
count++;
// other process
new Thread(new SubTask(countDownLatchWithNoCount )).start();
}
countDownLatchWithNoCount.await(count);
}
/**
* task
*/
private static class SubTask implements Runnable{
private CountDownLatchWithNoCount countDownLatchWithNoCount;
public SubTask(CountDownLatchWithNoCount countDownLatchWithNoCount) {
this.countDownLatchWithNoCount = countDownLatchWithNoCount;
}
@Override
public void run() {
// other process
countDownLatchWithNoCount.countDown();
}
}
主要代码:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
/**
* <p>锁</p>
*
* 解决CountDownLatch必须在声明时指定线程数量的问题
*
* @author cuijr
* @since 2024/1/24 17:24
*/
@Slf4j
public class CountDownLatchWithNoCount {
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
private volatile int count = 0;
int getCount() {
return getState();
}
int getTargetCount() {
return count;
}
protected int tryAcquireShared(int acquires) {
return (count != 0 && getState() <= count) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
int nextc = c - 1;
if (compareAndSetState(c, nextc)) {
return count != 0 && nextc == count;
}
}
}
}
private final Sync sync;
/**
* Constructs a {@code CountDownLatch} initialized with the given count.
*
* @throws IllegalArgumentException if {@code count} is negative
*/
public CountDownLatchWithNoCount() {
this.sync = new Sync();
}
/**
* 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.
*
* @param count the number of times {@link #countDown} must be invoked
* before threads can pass through {@link #await}
* @throws InterruptedException if the current thread is interrupted
* while waiting
*/
public void await(int count) throws InterruptedException {
sync.count = 0 - count;
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(int count, long timeout, TimeUnit unit)
throws InterruptedException {
sync.count = 0 - count;
log.info("thread {}, going to wait for {}, and current is {}", Thread.currentThread().getName(), sync.getTargetCount(), sync.getCount());
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() {
log.info("thread {} countDown, and status is {}, and count is {}", Thread.currentThread().getName(), sync.getCount(), sync.getTargetCount());
sync.releaseShared(1);
}
/**
* Returns the current count.
*
* <p>This method is typically used for debugging and testing purposes.
*
* @return the current count
*/
public int getCount() {
return sync.getCount();
}
/**
* 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() + "]";
}
}