本章探讨线程安全的java平台本身的机制,免于基于同步(内部锁)或显式锁的实现,可以简化开发,避免锁造成的各种问题和开销。
- 无状态对象
- 不可变对象
- ThreadLoacl线程特有对象
- 线程安全并发集合
无状态对象
无状态对象,就是没有实例变量的对象.不能保存数据,是线程安全的。
比如以下方法中的变量都是方法内部的变量
public class AdderImpl implements AdderImplRemote {
public int add(int a,int b){
return a+b;
}
}
不可变对象Immutable Object
在创建状态后无法更改其状态的对象称为不可变对象。一个对象不可变的类称为不可变类。
不变的对象可以由程序的不同区域共享而不用担心其状态改变。不可变对象本质上是线程安全的。
以下不可变类创建对象后,只能读,不可写
public class IntWrapper {
private final int value;
public IntWrapper(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
自定义不可变类遵守如下原则:
- 1、使用private和final修饰符来修饰该类的属性(非必须)。
- 2、提供带参数的构造器,用于根据传入的参数来初始化属性。
- 3、仅为该类属性提供getter方法,不要提供setter方法。通过执行深度复制的构造函数初始化所有字段。执行getter方法中对象的克隆以返回副本,而不是返回实际的对象引用。
- 4、如果有必要,重写hashCode和equals方法,同时应保证两个用equals方法判断为相等的对象,其hashCode也应相等。
- 5、最好不允许类被继承(非必须)
一些类已经是不可变类,比如String
public class Testimmutablestring {
public static void main(String args[]){
String s="Abc";
s.concat(" Def");//concat() method appends the string at the end
System.out.println(s);//will print Abc because strings are immutable objects
//实际t和s是不同的对象,地址指向不同的堆空间
String t = s.concat(" Def");
System.out.println(t);
}
}
以下例子是浅复制造成的地址一致,testMap容易被意外改变。
import java.util.HashMap;
public class FinalClassExample {
private int id;
private double dou;
private String name;
private HashMap<String,String> testMap;
public int getId() {
return id;
}
public double getDou() {
return dou;
}
public String getName() {
return name;
}
public HashMap<String, String> getTestMap() {
return testMap;
}
/**
* 浅复制测试
* @param i
* @param n
* @param hm
*/
public FinalClassExample(int i, double d, String n, HashMap<String,String> hm){
System.out.println("对象初始化时浅复制");
this.id=i;
this.dou=d;
this.name=n;
this.testMap=hm;
}
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
double d = 100D;
FinalClassExample ce = new FinalClassExample(i,d,s,h1);
//Lets see whether its copy by field or reference
System.out.println(s==ce.getName());
System.out.println(h1 == ce.getTestMap());
//print the ce values
System.out.println("ce id:"+ce.getId());
System.out.println("ce name:"+ce.getName());
System.out.println("ce testMap:"+ce.getTestMap());
//change the local variable values
i=20;
s="modified";
d = 200D;
h1.put("3", "third");
//print the values again
System.out.println("i after local variable change:"+i);
System.out.println("d after local variable change:"+d);
System.out.println("s after local variable change:"+s);
System.out.println("s'hashCode after local variable change:"+s.hashCode());
System.out.println(s==ce.getName());
System.out.println(h1 == ce.getTestMap());
System.out.println("ce id after local variable change:"+ce.getId());
System.out.println("ce dou after local variable change:"+ce.getDou());
System.out.println("ce name after local variable change:"+ce.getName());
System.out.println("ce name'hashCode after local variable change:"+ce.getName().hashCode());
System.out.println("ce testMap after local variable change:"+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from accessor methods:"+ce.getTestMap());
}
}
对象初始化时浅复制
true
true
ce id:10
ce name:original
ce testMap:{1=first, 2=second}
i after local variable change:20
d after local variable change:200.0
s after local variable change:modified
s'hashCode after local variable change:-615513399
false
true
ce id after local variable change:10
ce dou after local variable change:100.0
ce name after local variable change:original
ce name'hashCode after local variable change:1379043793
ce testMap after local variable change:{1=first, 2=second, 3=third}
ce testMap after changing variable from accessor methods:{1=first, 2=second, 3=third, 4=new}
string和int不可变,是因为外部变量和类内部变量的地址指向本身就是不同。
下面用深复制,保证了对象不可变:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
/**
* Accessor function for mutable objects
*/
public HashMap<String, String> getTestMap() {
//return testMap;
return (HashMap<String, String>) testMap.clone();
}
/**
* Constructor performing Deep Copy
* @param i
* @param n
* @param hm
*/
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Deep Copy for Object initialization");
this.id=i;
this.name=n;
HashMap<String,String> tempMap=new HashMap<String,String>();
String key;
Iterator<String> it = hm.keySet().iterator();
while(it.hasNext()){
key=it.next();
tempMap.put(key, hm.get(key));
}
this.testMap=tempMap;
}
/**
* To test the consequences of Shallow Copy and how to avoid it with Deep Copy for creating immutable classes
* @param args
*/
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
//Lets see whether its copy by field or reference
System.out.println(s==ce.getName());
System.out.println(h1 == ce.getTestMap());
//print the ce values
System.out.println("ce id:"+ce.getId());
System.out.println("ce name:"+ce.getName());
System.out.println("ce testMap:"+ce.getTestMap());
//change the local variable values
i=20;
s="modified";
h1.put("3", "third");
//print the values again
System.out.println("ce id after local variable change:"+ce.getId());
System.out.println("ce name after local variable change:"+ce.getName());
System.out.println("ce testMap after local variable change:"+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from accessor methods:"+ce.getTestMap());
}
}
View Code
ThreadLoacl线程特有对象
ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储。用于创建只能由同一线程读写的线程局部变量。例如,如果两个线程正在访问引用同一个threadLocal变量的代码,则每个线程都不会看到其他线程对threadLocal变量所做的任何修改。
使用ThreadLocal可以避免锁的争用。
例子
public class Context {
private final String userName;
Context(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "Context{" +
"userNameSecret='" + userName + '\'' +
'}';
}
}
public class ThreadLocalWithUserContext implements Runnable {
private static final ThreadLocal<Context> userContext = new ThreadLocal<>();
private final Integer userId;
private UserRepository userRepository = new UserRepository();
ThreadLocalWithUserContext(Integer userId) {
this.userId = userId;
}
@Override
public void run() {
String userName = userRepository.getUserNameForUserId(userId);
userContext.set(new Context(userName));
System.out.println("thread context for given userId: " + userId + " is: " + userContext.get());
}
}
public class NoThreadLocalWithUserContext implements Runnable {
private static Context userContext = new Context(null);
private final Integer userId;
private UserRepository userRepository = new UserRepository();
NoThreadLocalWithUserContext(Integer userId) {
this.userId = userId;
}
@Override
public void run() {
String userName = userRepository.getUserNameForUserId(userId);
userContext = new Context(userName);
System.out.println("thread context for given userId: " + userId + " is: "
+ userContext.toString());
}
}
public class UserRepository {
String getUserNameForUserId(Integer userId) {
return UUID.randomUUID().toString();
}
}
public class ThreadLocalTest{
public static void main(String []args){
ThreadLocalWithUserContext firstUser = new ThreadLocalWithUserContext(1);
ThreadLocalWithUserContext secondUser = new ThreadLocalWithUserContext(2);
new Thread(firstUser).start();
new Thread(secondUser).start();
NoThreadLocalWithUserContext thirdUser = new NoThreadLocalWithUserContext(1);
NoThreadLocalWithUserContext fourthUser = new NoThreadLocalWithUserContext(2);
new Thread(thirdUser).start();
new Thread(fourthUser).start();
}
}
并发集合
ConcurrentHashMap
ConcurrentHashMap是一个线程安全的映射实现,除了HashTable或显式同步化或加锁HashMap之外,它还提供了在多线程环境中使用的另一种方法。ConcurrentHashMap是java.util.concurrent包的一部分
HashTable或HashMap上的显式同步,同步单个锁上的所有方法,并且所有方法都是同步的,即使方法用于检索元素。这使得运行效率非常缓慢。因为所有方法都是同步的,所以读取操作也很慢。ConcurrentHashMap试图解决这些问题。
ConcurrentHashMap内部实现不使用锁,而是CAS操作。
例子
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MapSynchro implements Runnable{
private Map<String, String> testMap;
public MapSynchro(Map<String, String> testMap){
this.testMap = testMap;
}
public static void main(String[] args) {
//Map<String, String> testMap = new HashMap<String, String>();
Map<String, String> testMap = new ConcurrentHashMap <String, String>();
/// 4 threads
Thread t1 = new Thread(new MapSynchro(testMap));
Thread t2 = new Thread(new MapSynchro(testMap));
Thread t3 = new Thread(new MapSynchro(testMap));
Thread t4 = new Thread(new MapSynchro(testMap));
t1.start();
t2.start();
t3.start();
t4.start();
try {
t1.join();
t2.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Size of Map is " + testMap.size());
}
@Override
public void run() {
System.out.println("in run method" + Thread.currentThread().getName());
String str = Thread.currentThread().getName();
for(int i = 0; i < 100; i++){
// adding thread name to make element unique
testMap.put(str+i, str+i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
CopyOnWriteArrayList
CopyOnWriteArrayList实现了List接口,与其他著名的ArrayList一样,它也是Java.util.concurrent包的一部分。CopyOnWriteArrayList与ArrayList的区别在于它是ArrayList的线程安全变体。当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
适用于遍历多于修改操作更频繁的情况。向CopyOnWriteArrayList中add方法的实现(向CopyOnWriteArrayList里添加元素),可以发现在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
所以添加操作频繁时,效率不高。CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。
- 内存占用问题
因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。
- 数据一致性问题
CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
以下例子会出现异常,如果ArrayList在迭代过程中被修改,那么将抛出ConcurrentModificationException
package io.github.viscent.mtia.ext;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CopyList {
public static void main(String[] args) {
//creating CopyOnWriteArrayList
List<String> carList = new ArrayList<String>();
carList.add("Audi");
carList.add("Jaguar");
carList.add("Mini Cooper");
carList.add("BMW");
Thread t1 = new Thread(new ItrClass(carList));//读
Thread t2 = new Thread(new ModClass(carList));//写
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("List elements in Main- " + carList);
}
}
//Thread class for iteration
class ItrClass implements Runnable{
List<String> carList;
public ItrClass(List<String> carList){
this.carList = carList;
}
@Override
public void run() {
Iterator<String> i = carList.iterator();
while (i.hasNext()){
System.out.println(i.next());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//Thread class for modifying list
class ModClass implements Runnable{
List<String> carList;
public ModClass(List<String> carList){
this.carList = carList;
}
@Override
public void run() {
System.out.println("Adding new value to the list");
carList.add("Mercedes");
}
}
Adding new value to the list
Audi
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at io.github.viscent.mtia.ext.ItrClass.run(CopyList.java:41)
at java.lang.Thread.run(Thread.java:745)
List elements in Main- [Audi, Jaguar, Mini Cooper, BMW, Mercedes]
换成这样就没问题了:
List<String> carList = new CopyOnWriteArrayList<String>();
CopyOnWriteArraySet
CopyOnWriteArraySet是一个Set接口实现,因此不允许重复元素。
CopyOnWriteArraySet是线程安全的。
由于CopyOnWriteArraySet在内部使用CopyOnWriteArrayList,所以就像在CopyOnWriteArrayList中一样,所有的变异操作(添加、设置等)都会创建基础数组的单独副本,这样就不会有线程干扰。
返回的迭代器是故障安全的,这意味着迭代器保证不会抛出ConcurrentModificationException,即使在迭代器创建后的任何时候对集合进行了结构修改。
迭代器的元素更改操作(如add、remove)不受支持,并引发UnsupportedOperationException。
注意点:
CopyOnWriteArraySet最适合于集较小、只读操作多于变更操作的应用程序,并且需要防止遍历期间线程之间的干扰。
由于增加了创建底层数组副本的任务,因此变更操作(添加、设置、删除等)成本高昂。
CopyOnWriteArraySet保证不会抛出ConcurrentModificationException,即使在迭代期间对集合进行了并发修改。同时,迭代器的元素更改操作(如remove)不受支持。
ConcurrentSkipListMap
ConcurrentSkipListMap是线程安全的有序的哈希表,适用于高并发的场景。
ConcurrentSkipListMap和TreeMap,它们虽然都是有序的哈希表。但是,第一,它们的线程安全机制不同,TreeMap是非线程安全的,而ConcurrentSkipListMap是线程安全的。第二,ConcurrentSkipListMap是通过跳表实现的,而TreeMap是通过红黑树实现的。
关于跳表(Skip List),它是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的。
public class SkipMapDemo {
public static void main(String[] args) {
// Creating ConcurrentSkipListMap
ConcurrentNavigableMap<Integer, String> numberMap = new ConcurrentSkipListMap<Integer, String>();
// Storing elements
numberMap.put(1, "ONE");
numberMap.put(2, "TWO");
numberMap.put(5, "FIVE");
numberMap.put(8, "EIGHT" );
numberMap.put(10, "TEN");
numberMap.put(16, "SIXTEEN");
System.out.println("** reverse order view of the map **");
//Returns a reverse order view of the mappings
ConcurrentNavigableMap<Integer, String> reverseNumberMap = numberMap.descendingMap();
Set<Map.Entry<Integer, String>> numSet = reverseNumberMap.entrySet();
numSet.forEach((m)->System.out.println("key " + m.getKey()
+ " value " + m.getValue()));
System.out.println("** First entry in the the map **");
//Returns a key-value mapping associated with the least key in this map
Map.Entry<Integer, String> mapEntry = numberMap.firstEntry();
System.out.println("key " + mapEntry.getKey() + " value " + mapEntry.getValue());
System.out.println("** Floor entry Example **");
//Returns a key-value mapping associated with the greatest key less than or equal to the given key
mapEntry = numberMap.floorEntry(7);
System.out.println("key " + mapEntry.getKey() + " value " + mapEntry.getValue());
System.out.println("** Ceiling entry Example **");
//Returns a key-value mapping associated with the least key greater than or equal to the given key
mapEntry = numberMap.ceilingEntry(7);
System.out.println("key " + mapEntry.getKey() + " value " + mapEntry.getValue());
}
}
** reverse order view of the map **
key 16 value SIXTEEN
key 10 value TEN
key 8 value EIGHT
key 5 value FIVE
key 2 value TWO
key 1 value ONE
** First entry in the the map **
key 1 value ONE
** Floor entry Example **
key 5 value FIVE
** Ceiling entry Example **
key 8 value EIGHT
ConcurrentLinkedQueue
ConcurrentLinkedQueue是一个线程安全的无边界队列。它将其元素存储为链接节点,其中每个节点存储对下一个节点的引用。
ConcurrentLinkedQueue与ArrayBlockingQueue、PriorityBlockingQueue等BlockingQueue实现的区别在于,ConcurrentLinkedQueue是非阻塞的,因此此队列中的操作不会阻塞。由于ConcurrentLinkedQueue是非阻塞的,因此没有put()或take()方法可以在需要时阻塞。
按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。
新的元素插入到队列的尾部,队列获取操作从队列头部获得元素。当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许使用 null 元素。
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ConcurrentLQ {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(4);
Queue<Integer> conQueue = new ConcurrentLinkedQueue<>();
// One Producer thread
executor.execute(new ConProducer(conQueue));
// Two Consumer thread
executor.execute(new ConConsumer(conQueue));
executor.execute(new ConConsumer(conQueue));
executor.shutdown();
}
}
//Producer
class ConProducer implements Runnable{
Queue<Integer> conQueue;
ConProducer(Queue<Integer> conQueue){
this.conQueue = conQueue;
}
@Override
public void run() {
for(int i = 0; i < 6; i++){
System.out.println("Adding to queue-" + i);
conQueue.add(i);
}
}
}
//Consumer
class ConConsumer implements Runnable{
Queue<Integer> conQueue;
ConConsumer(Queue<Integer> conQueue){
this.conQueue = conQueue;
}
@Override
public void run() {
for(int i = 0; i < 4; i++){
try {
TimeUnit.MILLISECONDS.sleep(50);
System.out.println("Thread Name -" + Thread.currentThread().getName() + " Consumer retrieved- " + conQueue.poll());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Adding to queue-0
Adding to queue-1
Adding to queue-2
Adding to queue-3
Adding to queue-4
Adding to queue-5
Thread Name -pool-1-thread-3 Consumer retrieved- 1
Thread Name -pool-1-thread-2 Consumer retrieved- 0
Thread Name -pool-1-thread-3 Consumer retrieved- 2
Thread Name -pool-1-thread-2 Consumer retrieved- 3
Thread Name -pool-1-thread-2 Consumer retrieved- 4
Thread Name -pool-1-thread-3 Consumer retrieved- 5
Thread Name -pool-1-thread-3 Consumer retrieved- null
Thread Name -pool-1-thread-2 Consumer retrieved- null
注意,最后的一次poll造成了取null,但不会抛出异常,是非阻塞的。
LinkedTransferQueue
LinkedTransferQueue是一个聪明的队列,他是 ConcurrentLinkedQueue,SynchronousQueue(in “fair” mode公平模式 ), and unbounded LinkedBlockingQueue 的超集。
LinkedTransferQueue 实现了一个重要的接口 TransferQueue, 该接口含有下面几个重要方法:
1. transfer(E e)
若当前存在一个正在等待获取的消费者线程,即立刻移交之;否则,会插入当前元素 e 到队列尾部,并且等待进入阻塞状态,到有消费者线程取走该元素。
2. tryTransfer(E e)
若当前存在一个正在等待获取的消费者线程(使用 take() 或者 poll() 函数),使用该方法会即刻转移 / 传输对象元素 e ;若不存在,则返回 false ,并且不进入队列。这是一个不阻塞的操作。
3. tryTransfer(E e, long timeout, TimeUnit unit)
若当前存在一个正在等待获取的消费者线程,会立即传输给它 ; 否则将插入元素 e 到队列尾部,并且等待被消费者线程获取消费掉 , 若在指定的时间内元素 e 无法被消费者线程获取,则返回 false ,同时该元素被移除。
4. hasWaitingConsumer()
判断是否存在消费者线程
5. getWaitingConsumerCount()
获取所有等待获取元素的消费线程数量
6. size()
因为队列的异步特性,检测当前队列的元素个数需要逐一迭代,可能会得到一个不太准确的结果,尤其是在遍历时有可能队列发生更改。
7. 批量操作
类似于addAll,removeAll,retainAll, containsAll, equals, toArray 等方法,API不能保证一定会立刻执行。
- LinkedTransferQueue是链接节点上的无限队列。
- 此队列针对任何给定的生产者排序元素FIFO(先进先出)。
- 元素被插入到尾部,并从队列的头部检索。
- 它提供阻塞插入和检索操作。
- 它不允许空对象。
- LinkedTransferQueue是线程安全的。
- 由于异步特性,size()方法不是一个常量时间操作,因此如果在遍历期间修改此集合,则可能会报告不准确的结果。
- 批量操作addAll、removeAll、retainAll、containsAll、equals和toArray不能保证以原子方式执行。例如,与addAll操作并发操作的迭代器可能只查看一些添加的元素。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TransferQueue;
public class LinkedTQ {
public static void main(String[] args) {
TransferQueue<Integer> tQueue = new LinkedTransferQueue<>();
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(new LinkedProducer(tQueue));
executor.execute(new LinkedConsumer(tQueue));
executor.shutdown();
}
}
//Producer
class LinkedProducer implements Runnable{
TransferQueue<Integer> tQueue;
LinkedProducer(TransferQueue<Integer> tQueue){
this.tQueue = tQueue;
}
@Override
public void run() {
for(int i = 0; i < 5; i++){
try {
System.out.println("Adding to queue-" + i);
tQueue.transfer(i);
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//Consumer
class LinkedConsumer implements Runnable{
TransferQueue<Integer> tQueue;
LinkedConsumer(TransferQueue<Integer> tQueue){
this.tQueue = tQueue;
}
@Override
public void run() {
for(int i = 0; i < 5; i++){
try {
System.out.println("Consumer retrieved- " + tQueue.take());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Adding to queue-0
Consumer retrieved- 0
Adding to queue-1
Consumer retrieved- 1
Adding to queue-2
Consumer retrieved- 2
Adding to queue-3
Consumer retrieved- 3
Adding to queue-4
Consumer retrieved- 4
LinkedTransferQueue和ConcurrentLinkedQueue和之前的集合类不同,这两个是用来做生产-消费同步队列的,LinkedTransferQueue用于阻塞异步队列,生产者可以等待消费者。ConcurrentLinkedQueue是非阻塞队列。
而前面的那些集合比如ConcurrentHashMap是线程安全的并发容器。
关于作者: 王昕在广州工作生活30余年。十多年开发经验,在Java、即时通讯、NoSQL、BPM、大数据等领域较有经验。