第六章 Concurrent Collections (并发集合类) 【上】

来源:互联网 发布:淘宝怎么看价格走势 编辑:程序博客网 时间:2024/06/16 07:25

本章涉及内容:

  • 使用非阻塞、线程安全lists集合
  • 使用阻塞、线程安全lists集合
  • 通过优先级确定顺序来使用阻塞式线程安全lists集合
  • 通过延迟元素来使用线程安全lists集合
  • 使用线程安全导航maps集合
  • 生成一个并发随机数
  • 使用原子变量
  • 使用原子数组

简介

数据结构是程序最基础的元素。Java API提供java 集合框架来管理数据,当你在选择集合类要特别小心,有些集合类并不支持并发功能,这样会导致数据不一致情况,例如是ArrayList类。

java也提供并发集合类框架,它大致可以分为两种并发类

阻塞集合: 没有取到元素会一直阻塞。

非阻塞集合: 没有获取到元素会立即返回null值或抛出一个异常。线程不会阻塞。

这章会涉及的类:

  1. 非阻塞list集合,使用ConcurrentLinkedDeque类实现
  2. 阻塞list集合,使用LinkedBlockingDeque类实现
  3. 阻塞list集合来实现生产者和消费者模型。使用LinkedTransferQueue类实现
  4. 通过延迟元素来实现阻塞list集合,使用DelayQueue类
  5. 非阻塞导航map,使用ConcurrentSkipListMap 类
  6. 随机数,使用ThreadLocalRandom类
  7. 原子变量和数组,使用AtomicLong和AtomicIntegerArray类

1、使用非阻塞式线程安全的list集合

ConcurrentLinkedDeque类

package com.jack;import java.util.concurrent.ConcurrentLinkedDeque;public class AddTask implements Runnable{private ConcurrentLinkedDeque<String> list;public AddTask(ConcurrentLinkedDeque<String> list) {super();this.list = list;}@Overridepublic void run() {String name = Thread.currentThread().getName();for (int i=0; i<10000; i++){list.add(name+ " : Element " + i);}}}

package com.jack;import java.util.concurrent.ConcurrentLinkedDeque;public class PollTask implements Runnable{private ConcurrentLinkedDeque<String> list;public PollTask(ConcurrentLinkedDeque<String> list) {super();this.list = list;}@Overridepublic void run() {for (int i=0; i<5000; i++){list.pollFirst();list.pollLast();}}}

package com.jack;import java.util.concurrent.ConcurrentLinkedDeque;public class Main {public static void main(String[] args) {ConcurrentLinkedDeque<String> list = new ConcurrentLinkedDeque<>();Thread threads[] = new Thread[100];for (int i=0; i<threads.length ; i++){AddTask task = new AddTask(list);threads[i] = new Thread(task);threads[i].start();}System.out.printf("Main : %d AddTask 线程已经启动了\n", threads.length);for (int i=0; i<threads.length; i++){try {threads[i].join();} catch (InterruptedException e){e.printStackTrace();}}System.out.printf("Main: List大小 : %d\n", list.size());for(int i=0; i< threads.length; i++){PollTask task = new PollTask(list);threads[i] = new Thread(task);threads[i].start();}System.out.printf("Main: %d PollTask 线程已经启动了\n", threads.length);for (int i=0; i<threads.length; i++){try {threads[i].join();} catch (InterruptedException e){e.printStackTrace();}}System.out.printf("Main : 大小集合: %d\n", list.size());}}

日志:

Main : 100 AddTask 线程已经启动了Main: List大小 : 1000000Main: 100 PollTask 线程已经启动了Main : 大小集合: 0

  • 总结:
  • 1、pollFirst移除第一个元素
  • 2、pollLast移除最后一个元素

扩展

  • 1、getFirst() 和getLast() 获取第一个元素和最后一个元素,并不会把元素移除,如果list集合为空,这个方法将会抛出NoSuchElementException异常。
  • 2、peek(),peekFirst() 和peekLast() 返回一个元素和最后一个元素。但是不会移除元素。如果集合为空,返回null值。
  • 3、remove(),removeFirst(), removeLast() 返回第一个元素和最后一个元素,会移除元素,如果为空,将会抛出NoSuchElementException异常。

2、使用阻塞线程安全的list集合

使用LinkedBlockingDeque实现阻塞list集合

package com.jack;import java.util.Date;import java.util.concurrent.LinkedBlockingDeque;import java.util.concurrent.TimeUnit;public class Client implements Runnable{private LinkedBlockingDeque<String> requestList;public Client(LinkedBlockingDeque<String> requestList) {super();this.requestList = requestList;}@Overridepublic void run() {for (int i=0; i<3; i++){for (int j=0; j<5; j++){StringBuilder request = new StringBuilder();request.append("[" + i);request.append(",");request.append(j +"]");try {requestList.put(request.toString());} catch (InterruptedException e){e.printStackTrace();}System.out.printf("客户端==添加的元素为%s  日期: %s.\n", request, new Date());}try{TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e){e.printStackTrace();}}System.out.printf("客户端: 执行完毕. \n");}}


总结:

1、创建一个LinkedBlockingDeque集合。

2、往里面添加元素 put() 如果有集合有大小限制,当它满了时候会阻塞

package com.jack;import java.util.Date;import java.util.concurrent.LinkedBlockingDeque;import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args) throws Exception{LinkedBlockingDeque<String> list = new LinkedBlockingDeque<>(3);Client client = new Client(list);Thread thread = new Thread(client);thread.start();for (int i=0; i<5; i++){for(int j=0; j<3; j++){String request = list.take();System.out.printf("Main: 拿出元素为 : %s 日期为: %s.  当前集合的大小: %d\n", request, new Date(), list.size());}TimeUnit.MILLISECONDS.sleep(300);}System.out.printf("Main : 执行完毕.\n");}}


总结:

  • 1、开启一个线程来向list集合添加元素
  • 2、主线程循环来获取元素take()方法,如果集合没有元素,那么它会有一直阻塞。

扩展:

  • 1、takeFirst() 和takeLast() 返回第一个和最后一个元素,同时会移除元素,如果集合为空,它会一直阻塞
  • 2、getFirst() 和getLast()  返回第一个和最后一个元素,不会移除元素,如果集合为空,它将会抛出NoSuchElementException异常。
  • 3、peek(), peekFirst(),和peekLast() 返回第一个和最后一个元素,不会移除元素,如果集合为空,它将返回null值
  • 4、poll(), pollFirst() 和pollLast()  返回第一个和最后一个元素,同时会移除元素,如果集合为空,它会返回null值
  • 5、add(), addFist()和addLast() 向第一个或最后一个位置添加元素,如果集合满了将会抛出IllegalStateException异常。

日志:


3、通过优先级使用阻塞线程安全有序list集合

通过PriorityBlockingQueue进行实现

package com.jack;public class Event implements Comparable<Event> {private int thread;private int priority;public Event(int thread, int priority) {super();this.thread = thread;this.priority = priority;}public int getThread() {return thread;}public int getPriority() {return priority;}@Overridepublic int compareTo(Event o) {if(this.priority>o.getPriority()){return -1;} else if(this.priority<o.getPriority()){return 1;}else{return 0;}}}

总结:创建一个Event实现Comparable接口,根据Priority进行比较

package com.jack;import java.util.concurrent.PriorityBlockingQueue;public class Task implements Runnable {private int id;private PriorityBlockingQueue<Event> queue;public Task(int id, PriorityBlockingQueue<Event> queue) {super();this.id = id;this.queue = queue;}@Overridepublic void run() {for (int i=0; i<1000; i++){Event event = new Event(id,i);queue.add(event);}}}

总结:创建一个PriorityBlockingQueue来添加元素,注意事件需要实现Comparable接口

package com.jack;import java.util.concurrent.PriorityBlockingQueue;public class Main {public static void main(String[] args) throws Exception{PriorityBlockingQueue<Event> queue = new PriorityBlockingQueue<>();Thread taskThreads[] = new Thread[5];for (int i=0; i<taskThreads.length; i++){Task task = new Task(i, queue);taskThreads[i] = new Thread(task);}for (int i=0; i<taskThreads.length; i++){taskThreads[i].start();}for (int i=0; i<taskThreads.length;i++){try{taskThreads[i].join();}catch (InterruptedException e){e.printStackTrace();}}System.out.printf("Main: 队列的大小: %d\n", queue.size());for(int i=0; i<taskThreads.length*1000; i++){Event event = queue.poll();System.out.printf("线程 %s: 优先级 %d\n", event.getThread(), event.getPriority(), event);}System.out.printf("Main : 队列大小: %d\n", queue.size());System.out.printf("Main: 执行完毕了");}}

总结:

  • 1、创建5个线程来执行添加任务
  • 2、taskThreads[i].join()表示等待所有线程添加任务完成之后进行后面的内容
  • 3、打印结果,会发现按照优先级继续排序。

扩展:

  • 1、clear() 移除队列所有元素
  • 2、take() 返回和移除第一个元素,如果队列中没有元素会一直阻塞
  • 3、put(E e) 插入一个元素
  • 4、peek(): 它会返回第一个元素,而不会移除它。

日志:



4、使用延迟来实现线程安全list集合

利用DelayedQueue类来实现延迟执行,但是必须要实现两个方法

compareTo(Delayed o): Delayed接口继承了Comparable接口

getDelay(TimeUnit unit) : 返回激活的延迟的时间

package com.jack;import java.util.Date;import java.util.concurrent.Delayed;import java.util.concurrent.TimeUnit;public class Event implements Delayed {private Date startDate;public Event(Date startDate) {super();this.startDate = startDate;}@Overridepublic int compareTo(Delayed o) {long result = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);if(result <0){return -1;} else if(result>0){return 1;}return 0;}@Overridepublic long getDelay(TimeUnit unit) {Date now = new Date();long diff = startDate.getTime() - now.getTime();return unit.convert(diff, TimeUnit.MILLISECONDS);}}

总结:

  • 1、这个Event实现接口 Delayed,然后实现两个方法 compareTo方法和getDelay()方法
  • 2、以开始时间-当前时间得到延迟时间,最后将延迟时间转成对应的单位covert(diff, TimeUnit.MiLLISECONDS) diff延迟时间的数值,第二个参数是延迟的单位,unit本身单位是转换的目标单位(TimeUnit.NANOSECONDS)。

package com.jack;import java.util.Date;import java.util.concurrent.DelayQueue;public class Task implements Runnable {private int id;private DelayQueue<Event> queue;public Task(int id, DelayQueue<Event> queue) {super();this.id = id;this.queue = queue;}@Overridepublic void run() {Date now = new Date();Date delay = new Date();delay.setTime(now.getTime() + (id*1000));System.out.printf("线程 %s 延迟了%s\n", id, delay);for (int i=0; i<100; i++){Event event = new Event(delay);queue.add(event);}}}

总结:主要设置延迟的时间为1秒,然后将它加入队列中。

package com.jack;import java.util.Date;import java.util.concurrent.DelayQueue;import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args) throws Exception{DelayQueue<Event> queue = new DelayQueue<>();Thread threads[] = new Thread[5];for(int i=0; i<threads.length; i++){Task task = new Task(i+1, queue);threads[i] = new Thread(task);}for(int i=0; i<threads.length; i++){threads[i].start();}for(int i=0; i<threads.length; i++){threads[i].join();}do{int counter =0;Event event;do{event = queue.poll();if(event != null){counter++;}}while (event !=null);System.out.printf("日期:%s 已经读取 %d 事件\n", new Date(), counter);TimeUnit.MILLISECONDS.sleep(500);}while (queue.size() >0);}}

总结:

  • 1、创建5个线程代表5个延迟任务。
  • 2、开始获取任务,判断获取任务时间是否为延迟之后的时间。

扩展:

  • 1、注意Queue.size()方法返回数量包含活动和非活动的元素
  • 2、clear() : 移除所有元素
  • 3、offer(E e) :插入一个元素
  • 4、peek(): 返回一个元素而不移除元素
  • 5、take() 返回元素并且移除元素。如果队列为空,线程会阻塞到有元素为止。

日志:

线程 5 延迟了Sat Aug 19 16:25:51 CST 2017线程 3 延迟了Sat Aug 19 16:25:49 CST 2017线程 4 延迟了Sat Aug 19 16:25:50 CST 2017线程 1 延迟了Sat Aug 19 16:25:47 CST 2017线程 2 延迟了Sat Aug 19 16:25:48 CST 2017日期:Sat Aug 19 16:25:46 CST 2017 已经读取 0 事件日期:Sat Aug 19 16:25:47 CST 2017 已经读取 0 事件日期:Sat Aug 19 16:25:47 CST 2017 已经读取 100 事件日期:Sat Aug 19 16:25:48 CST 2017 已经读取 0 事件日期:Sat Aug 19 16:25:48 CST 2017 已经读取 100 事件日期:Sat Aug 19 16:25:49 CST 2017 已经读取 0 事件日期:Sat Aug 19 16:25:49 CST 2017 已经读取 100 事件日期:Sat Aug 19 16:25:50 CST 2017 已经读取 0 事件日期:Sat Aug 19 16:25:50 CST 2017 已经读取 100 事件日期:Sat Aug 19 16:25:51 CST 2017 已经读取 0 事件日期:Sat Aug 19 16:25:51 CST 2017 已经读取 100 事件

阅读全文
0 0
原创粉丝点击