1.CountDownLatch
递减计数器锁,一个同步辅助类,可以用来确保某些活动直到其他活动都完成后才继续执行。它包含一个递减计数器,该计数器初始值是一个正数,表示需要等待事件的数量。
await()方法等待计数器到达0表示所需等待的事件都已完成。
例子:线程2等线程1执行完才能开始执行
CountDownLatch mLatch = new CountDownLatch(1);
new Thread() {
public void run() {
SystemClock.sleep(5 * 1000);
System.out.println("线程1执行结束");
mLatch.countDown();
};
}.start();
new Thread() {
public void run() {
try {
System.out.println("线程2等待线程1执行完毕");
mLatch.await();
System.out.println("线程2开始执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
SystemClock.sleep(15 * 1000);
System.out.println("线程2执行结束");
};
}.start();
2.CyclicBarrier
循环屏障,一个同步辅助类,它允许一组线程相互等待,直到达到某个公共屏障点之后再全部同时执行。当所有等待线程都被释放以后,CyclicBarrier可以被重用。
CyclicBarrier支持一个可选的Runnable命令,在一组线程中的最后一个线程达到之后,该命令只在每个屏障点运行一次。
例子:两个皮球装一箱,需要造10个皮球
int TOTAL_COUNT = 10;
int GROUP_COUNT = 2;
CyclicBarrier mBarrier = new CyclicBarrier(GROUP_COUNT, new Runnable() {
@Override
public void run() {
System.out.println("够一箱了,装箱...");
}
});
for (int i = 0; i < TOTAL_COUNT; i++) {
new Thread("线程" + i) {
public void run() {
try {
System.out.println(Thread.currentThread().getName()
+ "造皮球中...完毕后等待凑够一箱后装箱");
mBarrier.await();
System.out.println(Thread.currentThread().getName()
+ "开始装箱");
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
}
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们的侧重点不同。
CountDownLatch一般用于某个线程A等待若干个其他线程都执行完之后,线程A才执行。
而CyclicBarrier一般用于一组线程相互等待至某个状态,然后这一组线程同时执行。
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
3.semaphore
信号量,可以控制同时使用公共资源的线程个数。
semaphore管理着一个许可集,许可的初始数量可以通过构造函数来指定,执行操作时通过acquire()方法来获取许可,使用后通过release()方法释放许可。如果没有许可,则acquire()方法将会一直阻塞到有许可或被中断或者操作超时。
例子:10个人用一台打印机打印资料
Semaphore mSemaphore = new Semaphore(1);
for (int i = 0; i < 10; i++) {
new Thread(){
public void run() {
print("打印资料...");
};
}.start();
}
private void print(String str) {
try {
mSemaphore.acquire();
System.out.println("开始打印");
SystemClock.sleep(10 * 1000);
System.out.println("打印结束");
mSemaphore.release();
} catch (Exception e) {
e.printStackTrace();
}
}
4.FutureTask
未来任务,用来实现预加载资源。和Runnable类似,都可以通过Thread来启动。但是FutureTask可以返回执行完毕的结果,并且FutureTask的get()方法支持阻塞。我们可以用来预加载一些可能用到的资源,然后在需要用到的时候调用get()方法获取(如果资源加载完毕,则直接返回;否则会阻塞当前线程等待其加载完成)。
例子:加载新闻页或加载数据库信息
private volatile int mCurrentPage = 1;
private FutureTask<String> mFutureTask = new FutureTask<String>(
new Callable<String>() {
@Override
public String call() throws Exception {
return loadDateFromNet();
}
});
private String loadDateFromNet() {
SystemClock.sleep(2 * 1000);
return "第" + mCurrentPage + "页的数据是:***中500万大奖";
}
// 开始加载当前页数据
Thread thread = new Thread(mFutureTask);
thread.start();
for (int i = 0; i < 3; i++) {
long start = System.currentTimeMillis();
String currentPageContent = getCurrentPageContent();
System.out.println("耗时:" + (System.currentTimeMillis() - start)
+ ", 内容:" + currentPageContent);
SystemClock.sleep(5 * 1000);
}
private String getCurrentPageContent() {
String result = null;
try {
// 获取当前页的数据
result = mFutureTask.get();
// 新开启futureTask加载下一页的数据
mCurrentPage += 1;
Thread thread = new Thread(mFutureTask = new FutureTask<String>(
new Callable<String>() {
@Override
public String call() throws Exception {
return loadDateFromNet();
}
}));
thread.start();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
5.Executor
执行者,只有一个execute(Runnable command)回调接口。执行已提交的Runnable任务对象。此接口提供一个将任务提交与每个任务将如何运行相分离的机制。通常使用Executor而不是显式地创建线程。
Java类库提供了很多静态方法来创建线程池:
newFixedThreadPool创建一个固定长度的线程池。
newCachedThreadPool创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时会增加线程数量,线程池规模无限制。
newSingleThreadPoolExecutor创建一个单线程的Executor。
newScheduledThreadPool创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer。
在线程池中执行任务比为每个任务分配一个线程优势更多,通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊线程创建和销毁产生的巨大开销。当请求达到时,通常工作线程已经存在,提供了响应性;通过配置线程池的大小,可以创建足够多的线程使CPU达到忙碌状态,还可以防止线程太多耗尽计算机资源。
例子:创建长度为CPU个数的线程池来处理10个线程
Executor mExecutor = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors());
for (int i = 0; i < 10; i++) {
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName()
+ "执行");
SystemClock.sleep(3 * 1000);
}
};
mExecutor.execute(task);
}
6.ScheduledExecutorService
安排执行服务
Timer存在一些缺陷:
a.Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务且任务时间过长,超过了两个任务的间隔时间,那么后面线程需要等到前面线程执行完之后才能开始执行。
b.如果TimerTask抛出RuntimeException,Timer会停止所有使用Timer执行的任务。
c.Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化,会出现一些执行上的变化,ScheduledExecutorService基于时间的延迟,不会由于系统时间的改变发生执行变化。
例子:两个延迟任务,一个延迟1秒,1个延迟5秒
ScheduledExecutorService mScheduledExecutorService = Executors
.newScheduledThreadPool(2);
long mStartTime;
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("开始执行时间:"
+ (System.currentTimeMillis() - mStartTime));
SystemClock.sleep(10 * 1000);
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println("开始执行时间:"
+ (System.currentTimeMillis() - mStartTime));
SystemClock.sleep(2 * 1000);
}
};
mStartTime = System.currentTimeMillis();
mScheduledExecutorService.schedule(task1, 1000, TimeUnit.MILLISECONDS);
mScheduledExecutorService.schedule(task2, 5 * 1000,
TimeUnit.MILLISECONDS);
7.CompletionService
完成服务,它将Executor和BlockingQueue结合在一起,同时使用Callable作为任务的基本单元,整个过程就是生产者不断把Callable任务放入阻塞队列,Executor作为消费者不断把任务取出来执行并返回结果。哪个任务先执行完就直接返回,而不是按顺序返回,这样极大的提升了效率。
例子:声明一个线程池长度为3阻塞队列长度为2的CompletionService,并发执行10个任务
ExecutorService mExecutorService = Executors.newFixedThreadPool(3);
BlockingQueue<Future<Integer>> mBlockingQueue = new LinkedBlockingDeque<Future<Integer>>(
2);
CompletionService<Integer> mCompletionService = new ExecutorCompletionService<Integer>(
mExecutorService, mBlockingQueue);
for (int i = 0; i < 10; i++) {
mCompletionService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(1000);
SystemClock.sleep(num);
System.out.println(Thread.currentThread().getName() + "休眠了" + num);
return num;
}
});
}
for (int i = 0; i < 10; i++) {
try {
Future<Integer> take = mCompletionService.take();
System.out.println("执行完返回的结果:" + take.get());
} catch (Exception e) {
e.printStackTrace();
}
}
ExecutorService的invokeAll方法也能批量执行任务并返回结果,但是必须等待所有任务都执行完后才统一返回。