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

本文深入探讨Java中的并发集合类,包括非阻塞和阻塞线程安全的List集合、通过优先级确定顺序的阻塞式线程安全List集合、通过延迟元素实现的线程安全List集合等。通过具体实例讲解各种集合类的特点及应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本章涉及内容:

  • 使用非阻塞、线程安全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;
	}

	@Override
	public 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;
	}
	
	@Override
	public 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大小 : 1000000
Main: 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;
	}

	@Override
	public 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;
	}

	@Override
	public 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;
	}

	@Override
	public 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;
	}

	@Override
	public 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;
	}

	@Override
	public 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;
	}


	@Override
	public 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 事件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值