现有一个接口,每次请求后他会开启一组线程去处理业务,线程执行完大概要耗时1个小时左右,并会在执行的过程中消耗一定的硬件资源,如果10秒钟内有10个请求过来就将有10个线程同时执行,这无疑会对服务器造成一定压力,所以现在要进行限制,而相比直接使用sentinel进行限流,这里将会在后端的线程执行上进行处理。
实现方式
这里首先想到的就是使用是否可以使用CountDownLatch 进行解决,初始化为零,然后A线程开始执行的时候把值设成1,执行完了就设置成0,在A还没结束的时候来了线程B就要进行等待。
但是会有两种意外情况,第一种是两个线程同时获得CountDownLatch 的值,这时就会两个线程一块执行了。 第二种是假如A线程执行期间挂掉了,就相当于CountDownLatch 的值没有设回零,B线程就要一直等待了,但好在 await方法可以设置超时时间。
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
但仍存在问题,假如A线程挂掉了,此时BCD三个线程正在等待,如果他们超时时间都设置的1小时,那么时间一过这 仨都会开始执行,这样肯定有问题,所以就要把他们的超时时间设置成递增的,如B超时时间设成1小时,C设成两小时,D设成三个小时。
其实写到这里我就发现代码已经用了很多行了,然后换了一种方案,使用队列。
public class Test{
private static final AtomicReference<LinkedBlockingQueue<Integer>> LINKED_BLOCKING_QUEUE = new AtomicReference<>(new LinkedBlockingQueue<>(1));
private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);
....省略.....
new Thread(threadGroup, () -> {
//限制每次只有一个线程执行,超时后将继续执行,使超时时间自增能让超时等待的线程也能排队运行,确保只有一个在执行
try {
LINKED_BLOCKING_QUEUE.get().offer(1, ATOMIC_INTEGER.incrementAndGet(), TimeUnit.HOURS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
ATOMIC_INTEGER.set(ATOMIC_INTEGER.decrementAndGet());
}
....省略.....
使用阻塞队列,容量为1,ATOMIC_INTEGER 代表超时时间,基础值为1,
假设现在在线程A执行结束前会有线程BCD依次来执行, 单线A执行时队列还为空,所以不用等待,注意finally 块中来把超时时间置回零,线程B执行时开始阻塞,超时时间为1,线程C来时 也阻塞超时时间为2,D则递增到3。 这样就能尽最大限度来保证只有一个线程在执行。
线程执行结束时会把队列清空掉。
//要注意如果线程没有走到这一步就挂了 那么下次请求的时候即使没有线程在运行也会等待一定的时间
LINKED_BLOCKING_QUEUE.get().remove();
除了使用队列 还可以使用Jdk为我们提供的线程通讯工具类 Semaphore,它往往用于资源有限的场景中,去限制线程的数量。
如下面的例子,它可以限制始终只有三个线程在执行,其核心方法就是acquire() 和release(),用其限定资源同样可以达到需求。
public class SemaphoreDemo {
static class MyThread implements Runnable {
private final int value;
private final Semaphore semaphore;
public MyThread(int value, Semaphore semaphore) {
this.value = value;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire(); // 获取permit
System.out.printf("当前线程是%d, 还剩%d个资源,还有%d个线程在等待%n",
value, semaphore.availablePermits(), semaphore.getQueueLength());
// 睡眠随机时间,打乱释放顺序
Random random = new Random();
Thread.sleep(random.nextInt(1000));
System.out.printf("线程%d释放了资源%n", value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放permit
}
}
}
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(new MyThread(i, semaphore)).start();
}
}
}