> 文档中心 > 笔记 第14章 多线程(8)阻塞队列的使用与拷贝集合

笔记 第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》查找相关笔记

评论🌹点赞👍收藏✨关注👀,是送给作者最好的礼物,愿我们共同学习,一起进步

如果对作者发布的内容感兴趣,可点击下方关注公众号 钰娘娘知识汇总 查看更多作者文章哦!