笔记 第14章 多线程(8)阻塞队列的使用与拷贝集合
14.6 阻塞队列
可考虑使用队列来处理线程问题,比如银行问题可用一个队列来插入进行转账,这样银行问题就无需考虑同步。
常用方法:
阻塞方法:
put 队尾添加元素(注意!满阻塞)
take 队头移出元素(注意!空阻塞)
非阻塞无异常方法:
offer 添加返回是否成功,队未满true,队满false
peek 返回队头元素(不移除),空返回null
poll 队头移出元素,空返回null
空移除满添加抛异常:
add 添加到队尾,队满抛异常
elment 返回队头元素(不移除),空抛异常
remove 队头移出元素,空抛异常
阻塞队列的常见类:
LinkedBlockingDeque: 没有上边界,是双端队列
ArrayBlockingQueue: 需要指定容量,可以选是否公平(公平性能更低,长时间等待的线程会先处理)
PriorityBlockingQueue: 待优先级的队列,按照优先级顺序移除(可以学CPU多级反馈,处理次数多的优先级变低)
书作者给的示例:
❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤
import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.util.Scanner;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue; public class BlockingQueueTest { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.println("Enter bse directory (e.g. /usr/local/jdk1.6.0/src):"); String directory = in.nextLine(); System.out.print("Enter keyword (e.g. volatile):"); String keyword = in.nextLine(); final int FILE_QUEUE_SIZE = 10; final int SEARCH_THREADS = 100; BlockingQueue queue = new ArrayBlockingQueue(FILE_QUEUE_SIZE); //搜集目录下的所有文件 FileEnumerationTask enumerator = new FileEnumerationTask(queue, new File(directory)); new Thread(enumerator).start(); for(int i = 1; i <= SEARCH_THREADS; i++){ //不同线程使用同一个阻塞队列处理文件搜索(查了 ../../ 的 main,电脑差点崩了,尼瓜拉瀑布汗) new Thread(new SearchTask(queue,keyword)).start(); } }} class FileEnumerationTask implements Runnable{ public static File DUMMY = new File(""); private BlockingQueue queue; private File startingDirectory; public FileEnumerationTask(BlockingQueue queue, File startingDirectory){ this.queue = queue; this.startingDirectory = startingDirectory; } @Override public void run() { try{ enumerate(startingDirectory); queue.put(DUMMY); }catch (InterruptedException e){} } public void enumerate(File directory) throws InterruptedException{ File[] files = directory.listFiles(); if(files == null)return; for(File file:files){ if(file.isDirectory()) enumerate(file); else queue.put(file); } }} class SearchTask implements Runnable{ private BlockingQueue queue; private String keyword; public SearchTask(BlockingQueue queue, String keyword){ this.queue = queue; this.keyword = keyword; } @Override public void run() { try{ boolean done = false; while(!done){ File file =queue.take(); if(file == FileEnumerationTask.DUMMY){ queue.put(file); done = true; }else search(file); } }catch (IOException e){ e.printStackTrace(); }catch(InterruptedException e){} } public void search(File file) throws IOException{ Scanner in = new Scanner(new FileInputStream(file)); int lineNumber = 0; while(in.hasNextLine()){ lineNumber++; String line = in.nextLine(); if(line.contains(keyword)) System.out.printf("%s:%d:%s%n",file.getPath(),lineNumber,line); } in.close(); } }
❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤
输入:src
输入:main
输出结果:
🚗🚓🚕🛺🚙🚌🚐🚎🚑🚒🚚🚛🚜🚘🚔🚖🚍🦽🦼🛹🚲🛴🛵🏍
🚗🚓🚕🛺🚙🚌🚐🚎🚑🚒🚚🚛🚜🚘🚔🚖🚍🦽🦼🛹🚲🛴🛵🏍
我这个目录是用来写算法题的,就很多main函数
这个示例是做什么的呢?
1号线程用于做文件搜索(DFS)
余下所有线程用于根据给定关键词,搜索文件指定内容
由于数据都是从同一个阻塞队列取出的,所以唯一且不重复,因此可以多个线程一起处理
14.7 线程安全的集合
多线程同时并发修改一个数据结构,可能会产生不安全的操作,从而导致数据结构的破坏。比如一个线程要插入,另一个线程要遍历,无效引用会造成异常或者死循环
14.7.1 高效的的散列表、集合和队列
ConcurrentHashMap/ConcurrentSkipListMap/ConcurrentSkipListSet/ConcurrentLinkedQueue
特征:
锁的颗粒变小,不同处理模块可以同时处理,同一模块的读写线程可以同时处理,这样精细的划分方式,大大加快了多线程并发的处理速度
Size 的代价比较大,无法再 O(1) 时间完成
迭代器在遍历过程中不一定一致,但是最新且不会抛出异常
14.7.2 写数组的拷贝
对于某些情况,我们希望数据一定要一致,可以不是最新(比如转账,一个账户少10元一个账户多10元,这样的操作一定要保证总体的一致性,用户可以迟收到消息,但不能接受收到错误的消息)
这种情况下,我们考虑对原数组拷贝一份,修改完成后再进行数据替换,CopyOnWriteArrayList/CopeOnWriteArraySet
❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤
public class Main { public static void main(String[] args) throws InterruptedException { Main solution = new Main(); CopyOnWriteArrayList list = new CopyOnWriteArrayList(); for(int i = 0; i < 10; i++){ list.add(i); } Iterator it = list.iterator(); int j = 0; while(it.hasNext()){ System.out.print(it.next()+" "); list.add(++j); } System.out.println(); Iterator it2 = list.iterator(); while (it2.hasNext()){ System.out.print(it2.next()+" "); } System.out.println(); } }
❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤
结果:
🚗🚓🚕🛺🚙🚌🚐🚎🚑🚒🚚🚛🚜🚘🚔🚖🚍🦽🦼🛹🚲🛴🛵🏍
🚗🚓🚕🛺🚙🚌🚐🚎🚑🚒🚚🚛🚜🚘🚔🚖🚍🦽🦼🛹🚲🛴🛵🏍
迭代器遍历过程更新不会导致异常,但是迭代器无法获取到更新的数据
主要原理是add过程进行拷贝赋值,并更改了引用:
迭代器拿了一个引用的快照:
所以在迭代器过程中进行add,相当于是在旧引用进行遍历,新引用进行添加
相关内容:选择 《Java核心技术 卷1》查找相关笔记
评论🌹点赞👍收藏✨关注👀,是送给作者最好的礼物,愿我们共同学习,一起进步
如果对作者发布的内容感兴趣,可点击下方关注公众号 钰娘娘知识汇总 查看更多作者文章哦!