并发集合
当需要在并发程序中使用数据集合时,必须要谨慎的选择相应的实现方式。大多数集合类不能直接用于并发应用,因为他们没有对本身数据的并发访问进行控制。如果一些并发任务共享了一个不适用于并发任务的数据结构,将会遇到数据不一致的错误,并将影响程序的准确运行。这类数据结构的一个例子是arrayList类。
一般来说,Java提供了两类适用于并发应用的集合
阻塞式集合:这类集合包含添加和移除数据的方法。当集合已满或为空的时候,被调用的添加或移除方法就不能立即被执行,那么调用这个方法的线程将被阻塞,一直到该方法可以被成功执行。
非阻塞式集合:这类集合也包括添加和移除数据的方法,如果方法不能立即被执行,则返回null或抛出异常,但是调用这个方法的线程不会被阻塞。
非阻塞式列表对应的实现类:ConcurrentLinkedDeque类。
阻塞式列表对应的实现类:LinkedblockingDeque类。
用于数据生成或消费的阻塞式列表对应的实习类:LinkedTransferQueue类。
按优先级排序列表元素的阻塞式列表对应的实现类:PriorityBlockingQueue类。
带有延迟列表元素的阻塞式列表对应的实现类:DelayQueue类。
非阻塞式可遍历映射对应的实现类:ConcurrentSkipListMap类。
随机数字对应的实现类:ThreadLocalRandom类。
原子变量对应的实现类:AtomicLong和AtomicIntegerArray类。
使用非阻塞式线程安全列表:ConcurrentLinkedDeque
ConcurrentLinkedDeque<String> list=new ConcurrentLinkedDeque<>();
list.add(name+": Element "+i);
list.pollFirst();移除,如果列表为空,则返回null
list.pollLast();
Size()方法返回的值不一定是真实的,仅当没有任何线程修改列表时才能返回正确的结果。
getFirst()和getLast()。如果列标为空,则抛出NoSuchElementException异常。
Peek()、peekFirst()、peekLast(),如果列表为空,这些方法返回null,不会移除元素。
Remove、removeFirst()、removeLast(),如果列表为空这些方法返回null。
使用阻塞式线程安全列表:LinkedBlockingDeque
LinkedBlockingDeque list=new LinkedBlockingDeque<>(3);
list.put(request.toString());如果线程已满,调用这个方法的线程将被阻塞直到列标中有可用的空间。
String request=list.take();如果列标为空,调用这个方法的线程将被阻塞直到列表不为空
getFirst()、getLast()抛出NoSuchElementException异常。
使用按优先级排序的阻塞式线程安全列表:PriorityBlockingQueue
所有添加进PriorityBlockingQueue的元素必须实现Comparable接口。这个接口提供了compareTo()方法。使用compareTo()芳芳来决定插入元素的位置。
PriorityBlockingQueue的另一个重要的特性是:他是阻塞式数据结构。
package com.bh.recipe3.task;
/**
* This class stores the attributes of an event. Its thread
* and is priority. Implements the comparable interface to
* help the priority queue to decide which event has more priority
*
*/
public class Event implements Comparable<Event> {
/**
* Number of the thread that generates the event
*/
private int thread;
/**
* Priority of the thread
*/
private int priority;
/**
* Constructor of the thread. It initializes its attributes
* @param thread Number of the thread that generates the event
* @param priority Priority of the event
*/
public Event(int thread, int priority){
this.thread=thread;
this.priority=priority;
}
/**
* Method that returns the number of the thread that generates the
* event
* @return The number of the thread that generates the event
*/
public int getThread() {
return thread;
}
/**
* Method that returns the priority of the event
* @return The priority of the event
*/
public int getPriority() {
return priority;
}
/**
* Method that compares two events and decide which has more priority
*/
@Override
public int compareTo(Event e) {
if (this.priority>e.getPriority()) {
return -1;
} else if (this.priority<e.getPriority()) {
return 1;
} else {
return 0;
}
}
}
package com.bh.recipe3.task;
import java.util.concurrent.PriorityBlockingQueue;
/**
* This class implements a generator of events. It generates
* 1000 events and stores them in a priory queue
*
*/
public class Task implements Runnable {
/**
* Id of the task
*/
private int id;
/**
* Priority queue to store the events
*/
private PriorityBlockingQueue<Event> queue;
/**
* Constructor of the class. It initializes its attributes
* @param id Id of the task
* @param queue Priority queue to store the events
*/
public Task(int id, PriorityBlockingQueue<Event> queue) {
this.id=id;
this.queue=queue;
}
/**
* Main method of the task. It generates 1000 events and store
* them in the queue
*/
@Override
public void run() {
for (int i=0; i<1000; i++){
Event event=new Event(id,i);
queue.add(event);
}
}
}
package com.bh.recipe3.core;
import java.util.concurrent.PriorityBlockingQueue;
import com.bh.recipe3.task.Event;
import com.bh.recipe3.task.Task;
/**
* Main class of the example. Executes five threads that
* store their events in a common priority queue and writes
* them in the console to verify the correct operation of the
* PriorityBlockingQueue class
*
*/
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
/*
* Priority queue to store the events
*/
PriorityBlockingQueue<Event> queue=new PriorityBlockingQueue<>();
/*
* An array to store the five Thread objects
*/
Thread taskThreads[]=new Thread[5];
/*
* Create the five threads to execute five tasks
*/
for (int i=0; i<taskThreads.length; i++){
Task task=new Task(i,queue);
taskThreads[i]=new Thread(task);
}
/*
* Start the five threads
*/
for (int i=0; i<taskThreads.length; i++) {
taskThreads[i].start();
}
/*
* Wait for the finalization of the five threads
*/
for (int i=0; i<taskThreads.length; i++) {
try {
taskThreads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
* Write the events in the console
*/
System.out.printf("Main: Queue Size: %d\n",queue.size());
for (int i=0; i<taskThreads.length*1000; i++){
Event event=queue.poll();
System.out.printf("Thread %s: Priority %d\n",event.getThread(),event.getPriority());
}
System.out.printf("Main: Queue Size: %d\n",queue.size());
System.out.printf("Main: End of the program\n");
}
}
使用带有延迟元素的线程安全列表:delayQueue类
delayQueue类可以存放带有激活日期的元素,当代用方法从队列中返回或提取元素时,未来的元素日期将被忽略,这些元素对于这些方法是不可见的。
为了具有调用行为,存放到DelayQueue类中的元素必须继承Delayed接口。Delayed接口使对象成为延迟对象,它使存放在DelayQueue类中的对象具有了激活日期,即到激活日期的时间。该接口强制执行下列两个方法:
compareTo(Delayed o):Delayed接口继承了Comparable接口。
getDelay(TimeUtil util):这个方法返回到激活日期的剩余时间,单位由参数指定。
使用线程安全可遍历映射:ConcurrentNavigableMap接口及其实现类。实现这个接口的类以如下两部分存放元素:
一个键值(key),它是元素的标识并且是唯一的。
元素其他部分数据。
javaAPI也提供了一个实现ConcurrentSkipListMap接口的类,ConcurrentSkipListMap接口实现了与ConcurrentNavigableMap接口有相同的行为的一个非阻塞式列表。
生成并发随机数:ThreadLocalRandom类。它是线程本地变量,每个生成随机数的线程都有一个不同的生成器,但是都在同一个类中被管理,相比对于Random对象为所有线程生成随机数,这种机制具有更好的性能。
使用ThreadLocalRandom的current()方法,该方法是一个静态方法。
ThreadLocalRandom.current().nextInt(10) 改方法返回一个介于1-10之间的随机数。
使用原子数组:AtomicIntegetArray
更优的性能共享对象,Java引入了比较和交换操作,这个操作使用以下三步修改变量的值:
1.取得变量值,即变量的旧值。
2.在一个临时变量中修改变量值,即变量的新值。
3.如果上面获得的变量旧值与当前变量值相等,就用新值替换旧值。如果已有其他线程修改了这个变量的值,上面获得的变量的旧值就可能与当前变量值不同。
采用比较和交换机制不需要使用同步机制,不仅可以避免死锁而且性能更好。