在工程项目中可能会有这么一个场景,客户端处理层需要从服务端(CDN/图片服务器)获取n张图片(参考微博一个人最多有9张图片),那么问题来了,如何在一定的时间范围内尽可能多的获取到图片。当然,最为简单粗暴的方法就是通过串行的方式来获取,但是如果第一个请求hang住并超时,那么其他图片都无法获取,这显然不是一个好的设计方式。这个问题设计到两点,第一点是"尽可能多",相比于串行,解决方案就是多线程并行;第二点是"一定时间范围内",也就是超时时间,因此这个问题就可以抽象为多线程在超时范围内获取数据的方法,下面探讨几张方法。
一、thread.join(long millis) (不满足需求)
在JavaDoc中join(time)功能为在线程退出之前将会最多等待time毫秒。事实上,如果超时时间过了,那么主线程将会继续停止阻塞(可见join方法是阻塞的)并继续执行,这么做有一个弊端可能会造成所谓的"线程泄露"。此外,我们通常会调用interrupt来中断线程,当然这么做也是不提倡的。
TimeoutDemo.java:
public class TimeoutDemo {
public static void main(String[] args) {
try {
int threadCount = 3;
int defaultTimes = 10;
Thread[] ts = new Thread[threadCount];
for (int i=0; i < threadCount; i++) {
ts[i] = new Thread(new TimeoutRunnable(defaultTimes));
}
for (int i=0; i < threadCount; i++) {
ts[i].start();
}
for (int i=0; i < threadCount; i++) {
ts[i].join(1000);
System.out.println("i = " + i + " has joined...");
}
for (int i=0; i < threadCount; i++) {
ts[i].interrupt();
}
} catch (Exception e) {
System.out.println("thread interrupted when waiting join.");
} finally {
System.out.println("all threads finished...");
}
}
}
class TimeoutRunnable implements Runnable{
private int times;
public TimeoutRunnable(int times) {
this.times = times;
}
@Override
public void run() {
for (int i = 0; i < times; i++) {
System.out.println("thread id: " + Thread.currentThread().getId() + " runs for " + (i+1) + "th time.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("thread id: " + Thread.currentThread().getId() + " is interrupted while running and stop right now.");
return;
}
}
System.out.println("thread id: " + Thread.currentThread().getId() + " finished.");
}
}
运行结果:
thread id: 11 runs for 1th time.
thread id: 13 runs for 1th time.
thread id: 12 runs for 1th time.
i = 0 has joined...
thread id: 12 runs for 2th time.
thread id: 11 runs for 2th time.
thread id: 13 runs for 2th time.
i = 1 has joined...
thread id: 11 runs for 3th time.
thread id: 12 runs for 3th time.
thread id: 13 runs for 3th time.
i = 2 has joined...
thread id: 13 runs for 4th time.
thread id: 11 runs for 4th time.
thread id: 12 runs for 4th time.
thread id: 11 is interrupted while running and stop right now.
thread id: 13 is interrupted while running and stop right now.
all threads finished...
thread id: 12 is interrupted while running and stop right now.
可见,join方法是阻塞的,依次等待线程超时,就本例来讲,等待了3个1000ms,不满足在一定的timeout内完成。
二、基于ExecutorService的shutdown() 和 awaitTermination(long timeout, TimeUnit unit)方法:
awaitTermination是一个阻塞方法,在ExecutorService执行完shutdown之后进行操作,awaitTermination将会阻塞,直到所有任务完成了执行,或者超时产生,或者线程中断发生为止。如果所有任务完成则返回true;如果在任务完成之前发生了超时则返回false。代码示例,TimeoutDemo1.java:
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TimeoutDemo1 {
public static void main(String[] args) {
try {
int threadCount = 10;
int defaultTimes = 10;
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
for (int i=0; i < threadCount * 2; i++) {
executorService.submit(new TimeoutRunnable1(defaultTimes));
}
executorService.shutdown();
if(!executorService.awaitTermination(7, TimeUnit.SECONDS)) {
System.out.println("executors did not terminate in the specified time.");
List<Runnable> droppedTasks = executorService.shutdownNow();
System.out.println("executors was abruptly shut down. " + droppedTasks.size() + " tasks will not be executed.");
}
} catch (Exception e) {
System.out.println("exception: " + e.getMessage());
} finally {
System.out.println("scheduled executor service closed normally.");
}
}
}
class TimeoutRunnable1 implements Runnable{
private int times;
public TimeoutRunnable1(int times) {
this.times = times;
}
@Override
public void run() {
for (int i = 0; i < times; i++) {
System.out.println("thread id: " + Thread.currentThread().getId() + " runs for " + (i+1) + "th time.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("thread id: " + Thread.currentThread().getId() + " is interrupted while running and stop right now.");
return;
}
}
System.out.println("thread id: " + Thread.currentThread().getId() + " finished.");
}
}
关于shutdown和shutdonwNow的区别参考前一篇文章:如何优雅的关闭线程池。
三、使用CountDownLatch中的await(long timeout, TimeUnit unit)
如果在timeout时间范围内,count变成了0,那么await返回true;如果超过了超时时间,count不为0,那么返回false。
看代码TimeoutDemo2.java:
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TimeoutDemo2 {
public static void main(String[] args) {
try {
int threadCount = 10;
int defaultTimes = 10;
CountDownLatch countDownLatch = new CountDownLatch(threadCount * 2);
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
for (int i=0; i < threadCount * 2; i++) {
executorService.submit(new TimeoutRunnable2(defaultTimes, countDownLatch));
}
if(!countDownLatch.await(19, TimeUnit.SECONDS)) {
executorService.shutdown();
System.out.println("executors did not terminate in the specified time.");
List<Runnable> droppedTasks = executorService.shutdownNow();
System.out.println("executors was abruptly shut down. " + droppedTasks.size() + " tasks will not be executed.");
}
} catch (Exception e) {
System.out.println("exception: " + e.getMessage());
} finally {
System.out.println("scheduled executor service closed normally.");
}
}
}
class TimeoutRunnable2 implements Runnable{
private int times;
private CountDownLatch countDownLatch;
public TimeoutRunnable2(int times, CountDownLatch countDownLatch) {
this.times = times;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
for (int i = 0; i < times; i++) {
System.out.println("thread id: " + Thread.currentThread().getId() + " runs for " + (i + 1) + "th time.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("thread id: " + Thread.currentThread().getId() + " is interrupted while running and stop right now.");
return;
}
}
System.out.println("thread id: " + Thread.currentThread().getId() + " finished.");
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
countDownLatch.countDown();
}
}
}
四、Future.get(long timeout, TimeUnit unit)(不完全满足需求,不最优)
future.get在一定的超时时间范围内获取线程执行的返回值,但是get方法是阻塞的,会在一定的超时时间之内阻塞,直到任务完成返回,或者这时间超时抛出TimeoutException,代码如下:
TimeoutDemo3.java:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class TimeoutDemo3 {
public static void main(String[] args) {
try {
int threadCount = 10;
int defaultTimes = 10;
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
List<Future> futureList = new ArrayList<Future>();
for (int i=0; i < threadCount; i++) {
Future future = executorService.submit(new TimeoutRunnable3(defaultTimes));
futureList.add(future);
}
for (Future future: futureList) {
String str = (String)future.get(15, TimeUnit.SECONDS);
System.out.println(str);
}
} catch (InterruptedException e) {
System.out.println("future is interrupted...");
} catch (ExecutionException e) {
System.out.println("future execution exception...");
} catch (TimeoutException e) {
System.out.println("future timeout exception...");
} finally {
System.out.println("scheduled executor service closed normally.");
}
}
}
class TimeoutRunnable3 implements Callable<String>{
private int times;
public TimeoutRunnable3(int times) {
this.times = times;
}
@Override
public String call() {
try {
for (int i = 0; i < times; i++) {
System.out.println("thread id: " + Thread.currentThread().getId() + " runs for " + (i + 1) + "th time.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("thread id: " + Thread.currentThread().getId() + " is interrupted while running and stop right now.");
}
}
//System.out.println("thread id: " + Thread.currentThread().getId() + " finished.");
} catch (Exception e) {
System.out.println(e.getMessage());
}
return "thread id: " + Thread.currentThread().getId() + " finished";
}
}
这么做的问题在于,一旦最前面的线程阻塞了,超时了并抛出异常,那么其他完成的线程都无法拿到结果。
Author:忆之独秀
Email:leaguenew@qq.com
注明出处:https://blog.csdn.net/lavorange/article/details/80966938