Java核心技术笔记-第14章(2)

时间:2022-12-28 13:53:23

6.阻塞队列:用来控制各个线程之间协调工作,可以不需要显示的线程同步,而是使用队列数据结构作为一种同步机制。

7.线程安全的集合
(1)java.util.concurrent包中提供了ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet, ConcurrentLinkedQueue
(2)CopyOnWriteArrayList, CopyOnWriteArraySet。当构建一个迭代器的时候,迭代器包含一个对当前数组的引用,如果后来数组被修改了,迭代器依然引用原来的数组,但是集合的数组已经被替换了。
(3)[1]最开始Vector和Hashtable类提供了线程安全的动态数组和散列表,现在用ArrayList和HashMap类来代替他们(但是他们并不是线程安全的),并且要使用同步包装器使之成为线程安全的。如:

List<E> synchArrayList = Collections.synchronizedList(new ArrayList<E>());
Map<K,V> synchHashMap = Collections.synchronizedMap(new HashMap<K,V>());

注意:要确保不保存任何对原始对象的引用,要确保引用一个同步包装器包装过的类型。
[2]在对一个使用同步包装器包装过得对象进行迭代时,要进行“客户端”锁定。因为如果在迭代过程中另外一个线程想要修改该对象,就会造成迭代器失效。

synchronized(synchHashMap)      //对synchHashMap锁定
{
Iterator<K> iter = synchHashMap.keySet().iterator();
while(iter.hasNext()){...}
}

[3]最好不使用同步包装器,而是使用java.util.concurrent包中定义的集合。(有一个例外,如果一个数组列表经常被修改,那么同步的ArrayList胜过CopyOnWriteArrayList)

8.Callable和Future
(1)Callable和Runnable类似,但是Callable中是call方法而不是run方法,而且call方法可以有返回值,而run方法没有返回值。
(2)Future保存异步计算的结果。FutureTask包装器可将Callable转换为Runnable和Future:

Callable<Integer> myComputation = ...;
FutureTask<Integer> task = new FutureTask<Integer>(myComputation); //将一个Callable对象传给FutureTask对象,此时FutureTask对象既可以代表Runnable,也可以代表Future
Thread t = new Thread(task); //task此时相当于一个Runnable
t.start();
Integer res = task.get(); //task此时相当于一个Future,get是Future的方法。如果还没有计算出结果,get方法将被阻塞,直到计算出结果。

9.执行器
(1)使用线程池步骤
[1]创建线程池(Executors的静态方法):newCachedThreadPool,必要时创建新线程;newFixedThreadPool创建具有固定大小的线程池;newSingleThreadExecutor大小为1的线程池;(这三个方法都返回实现了ExecutorService接口的ThreadPoolExecutor类的对象)
[2]使用submit方法提交Runnable或者Callable对象,把任务加入到线程池中。submit方法将返回Future类型的对象,用来查询该任务的状态。
[3]如果想要取消一个任务,就要保存好submit方法返回的Future对象。
[4]不再提交任何任务时,调用shutdown方法。另外有个shutdownNow方法,将会取消尚未开始的所有任务并尝试中断正在运行的任务。
(2)预定执行
Executors类的newScheduledThreadPool和newSingleThreadScheduledExecutor方法返回实现了ScheduledExecutorService接口的对象,是一种允许使用线程池机制的java.util.Timer的泛化,可以预定Runnable或者Callable在初始的延迟之后只运行一次,也可以预定一个Runnable对象周期性的运行。
(3)控制任务组
[1]invokeAny方法提交包含所有Callable对象的集合,并返回某个已经完成了任务的结果(不能确定究竟是哪个任务的)。
[2]invokeAll方法提交包含所有Callable对象的集合,并返回一个Future对象的列表。如:

        List<Callable<T>> tasks = ...;
List<Future<T>> results = executors.invokeAll(tasks);
for(Future<T> result : result)
ProcessFurther(result.get());
[3]invokeAll方法无法按 获得结果(任务完成)的顺序将Future对象保存在列表中,可以使用ExecutorComplementionService。如:
        ExecutorComplementionService  service = new ExecutorComplementionService(executor);
for(Callable<T> task : tasks) service.submit(task);
for(int i = 0; i<tasks.size(); i++)
{
processFurther(service.take().get());
}

(4)Fork-Join框架(将一个问题分解,最后将结果合并来解决问题)
需要提供一个扩展RecursiveTask<T>的类(如果计算会生成一个类型为T的结果)或者提供一个扩展RecursiveAction的类(如果计算不生成任何结果),然后覆盖compute方法,在compute方法中,使用invoke接收任务并阻塞直到所有任务完成,最后使用join合并结果。

10.同步器
CyclicBarrier:线程运行到某一个共同的点就停止,直到所有线程到达这个点,然后采取下一步动作
CountDownLatch:让一个线程集等待,直到计数(具有某种实际意义)变为0
Exchanger:允许两个对象在要交换的对象准备好时,交换对象。比如,两个线程处理同一数据结构的不同实例,在两个线程都处理完毕时,交换实例。
Semaphore:允许线程等待直到被允许继续运行为止
SynchronousQueue:允许一个线程把对象交给另一个线程