第五章 基础构建模块
5.1 同步容器类
5.1.1 同步容器类的问题
如下,如果list含有10个元素,线程A调用getLast的同时线程B调用deleteLast,那么getLast可能会报ArrayIndexOutOfBoundsException
改为如下方式能确保size和get一致
Vector迭代也可能引发异常
改进后的方式安全了,但降低了并发性
5.1.3 隐藏迭代器
如下可能抛出ConcurrentModificationException,以内printn ……+set 这步操作会对set进行迭代,那么在并发调用addTenThings情况下,可能出现线程A迭代set,线程B修改set的情况,所以报出ConcurrentModificationException
5.2.1 ConcurrentHashMap
采用分段锁、吞吐性能更高
5.4 阻塞方法与中断方法
当A线程发送中断信号给到B线程,B线程可以选择向上层传递或者在catch块恢复中断状态(catch后中断状态会被重置回原来的状态),让上层代码能看到这个状态
5.5 同步工具类
同步工具类可以是任何一个对象,只要它根据其自身的状态来协调线程的控制流。阻塞队列可以作为同步工具类,其他类型的同步工具类还包括信号量 (Semaphore)、栅栏(Barrier)以及闭锁 (Latch)。
5.5.1 闭锁
CountDownLatch
CountDownLatch用法,测试N个线程并发执行某个任务时需要的时间。
public class TestHarness {
public static void main(String[] args) throws InterruptedException {
timeTasks(10,null);
}
public static long timeTasks(int nThreads, final Runnable task) throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
for (int i = 0;i {
try {
startGate.await();
try {
//task.run();
System.out.println("countDown测试");
} finally {
endGate.countDown();
}
} catch (InterruptedException e) {
//throw new RuntimeException(e);
}
});
t.start();
}
long start = System.nanoTime() ;
System.out.println("start:"+start);
startGate.countDown();
endGate.await();
long end = System.nanoTime ();
System.out.println("end:"+end);
return end-start;
}
}
5.5.2 FutureTask也可用作闭锁
通过Callable实现,相当于一种可生成结果的Runnable。FutureTask没完成的话get()方法会阻塞
public class Preloader {
public static void main(String[] args) throws InterruptedException{
Preloader preloader=new Preloader();
preloader.start();
System.out.println("调用get方法");
preloader.get();
}
private final FutureTask future =
new FutureTask(new Callable() {
public ProductInfo call() {
return loadProductInfo();
}
});
private final Thread thread = new Thread(future);
public void start() {
thread.start();
}
public ProductInfo get() throws InterruptedException {
try {
ProductInfo productInfo = future.get();
System.out.println("得到future返回:"+productInfo);
return productInfo;
} catch (ExecutionException e) {
Throwable cause = e.getCause();
return null;
}
}
public ProductInfo loadProductInfo () {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return null;
}
}
class ProductInfo {
}
Callable表示的任务可以抛出异常或者Error,这些异常都会被封装为 ExecutionException,作为Throwable类返回
public class Preloader {
public static void main(String[] args) throws InterruptedException{
Preloader preloader=new Preloader();
preloader.start();
System.out.println("调用get方法");
preloader.get();
}
private final FutureTask future =
new FutureTask(new Callable() {
public ProductInfo call() throws NumberFormatException{
return loadProductInfo();
}
});
private final Thread thread = new Thread(future);
public void start() {
thread.start();
}
public ProductInfo get() throws InterruptedException {
try {
ProductInfo productInfo = future.get();
System.out.println("得到future返回:"+productInfo);
return productInfo;
} catch (ExecutionException e) {
Throwable cause = e.getCause();
System.out.println("获取异常");
e.printStackTrace();
return null;
}
}
public ProductInfo loadProductInfo () throws NumberFormatException{
try {
Integer.valueOf("a");
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return null;
}
}
class ProductInfo {
}
5.5.3 信号量Semaphore
用于控制同时访问某个特定资源的操作数量、互斥锁、连接池、将容器变成有界阻塞容器
5.5.4 栅栏CyclicBarrier
转载自:CyclicBarrier 使用详解 - 简书
从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。
它的作用就是会让所有线程都等待完成后才会继续下一步行动。
举个例子,就像生活中我们会约朋友们到某个餐厅一起吃饭,有些朋友可能会早到,有些朋友可能会晚到,但是这个餐厅规定必须等到所有人到齐之后才会让我们进去。这里的朋友们就是各个线程,餐厅就是 CyclicBarrier。
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
解析:
一个线程组的线程需要等待所有线程完成任务后再继续执行下一次任务
public class CyclicBarrierDemo {
static class TaskThread extends Thread {
CyclicBarrier barrier;
public TaskThread(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(getName() + " 到达栅栏 A");
barrier.await();
System.out.println(getName() + " 冲破栅栏 A");
Thread.sleep(2000);
System.out.println(getName() + " 到达栅栏 B");
barrier.await();
System.out.println(getName() + " 冲破栅栏 B");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int threadNum = 5;
CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 完成最后任务");
}
});
for(int i = 0; i < threadNum; i++) {
new TaskThread(barrier).start();
}
}
}
打印结果:
Thread-1 到达栅栏 A
Thread-3 到达栅栏 A
Thread-0 到达栅栏 A
Thread-4 到达栅栏 A
Thread-2 到达栅栏 A
Thread-2 完成最后任务
Thread-2 冲破栅栏 A
Thread-1 冲破栅栏 A
Thread-3 冲破栅栏 A
Thread-4 冲破栅栏 A
Thread-0 冲破栅栏 A
Thread-4 到达栅栏 B
Thread-0 到达栅栏 B
Thread-3 到达栅栏 B
Thread-2 到达栅栏 B
Thread-1 到达栅栏 B
Thread-1 完成最后任务
Thread-1 冲破栅栏 B
Thread-0 冲破栅栏 B
Thread-4 冲破栅栏 B
Thread-2 冲破栅栏 B
Thread-3 冲破栅栏 B
从打印结果可以看出,所有线程会等待全部线程到达栅栏之后才会继续执行,并且最后到达的线程会完成 Runnable 的任务。
可以用于多线程计算数据,最后合并计算结果的场景。
5.6 构建高效且可伸缩的结果缓存
第六章 任务执行
6.1 在线程中执行任务
6.2.3 线程池
在线程池中执行任务比“为每个任务分配一个线程”优势更多。通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。另一个额外的好处是,当请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延迟任务执行,提高了响应性。通过适当调整线程池的大小,可以创建足够多的线程以便使处理器保持忙碌状态,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存或失败。
newFixedThreadPool。newFixedThreadPool将创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不再变化(如果某个线程由于发生了未预期的 Exception 而结束,那么线程池会补充一个新的线程)。
newCachedThreadPool。newCachedThreadPool将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,而当需求增加时,则可以添加新的线
程,线程池的规模不存在任何限制。
newSingleThreadExecutor。newSingleThreadExecutor是一个单线程的 Executor,它创建单个工作者线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。newSingleThreadExecutor能确保依照任务在队列中的顺序来串行执行(例如 FIFO、LIFO优先级)。
newScheduledThreadPool。newScheduledThreadPool 创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于 Timer
newWorkStealingPool://todo
6.2.4 Executor的生命周期
JVM 只有在所有(非守护)线程全部终止后才会退出。因此,如果无法正确地关闭 Executor,那么JVM 将无法结束。
6.3.2 携带结果的任务 Callable 与 Future
要使用 Callable 来表示无返回值的任务,可使用 Callable
Executor 执行的任务有 4 个生命周期阶段:创建、提交、开始和完成
get 方法的行为取决于任务的状态(尚未开始、正在运行、已完成)。如果任务已经完成,那么 get 会立即返回或者抛出一个 Exception,如果任务没有完成,那么get 将阻塞并直到任务完成。如果任务抛出了异常,那么 get 将该异常封装为 ExecutionException 并重新抛出。如果任务被取消,那么get 将抛出 CancellationException。如果 get 抛出了 ExecutionException,那么可以通过 getCause 来获得被封装的初始异常。
future.cancle(true) 取消任务
使用future等待图像下载
6.3.5 CompletionService;Executor 与 BlockingQueue
采用CompletionService实现边下载图片边加载图片的功能(下载好一个图片就加载一个图片),内部实现采用了队列
6.3.7为任务设置时限
线程任务执行超时直接取消,通过future.get()设置超时时间
使用invokeAll提交一组任务,返回一组结果List