Java多线程开发系列-线程安全设计

本章探讨线程安全的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是线程安全的并发容器。

posted @ 2020-03-13 21:20  昕友软件开发  阅读(355)  评论(0编辑  收藏  举报
欢迎访问我的开源项目:xyIM企业即时通讯