这里我把ConcurrentLinkedDeque与List进行对比测试了一下,发现在多线程情况下一般的集合会出现很大的并发性问题,下面就一起探索一下
1.使用ConcurrentLinkedDeque实现的多线程读写数据
任务:添加大量的数据到一个列表集合中
从同一个列表中移除大量的数据

/**
 * 
 * @author fcs
 * @date 2015-6-21
 * 描述:向集合中添加元素,添加10000个
 * 说明:
 */
public class AddTask implements Runnable{

    private ConcurrentLinkedDeque<String> list;

    public AddTask(ConcurrentLinkedDeque<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        for(int i =0;i<10000;i++){
            list.add(name+": Element "+i);
        }
    }

}

/**
 * 
 * @author fcs
 * @date 2015-6-21
 * 描述:从集合中取出元素
 * 说明:
 */
public class PollTask implements Runnable{

    private ConcurrentLinkedDeque<String> list;

    public PollTask(ConcurrentLinkedDeque<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        for(int  i =0;i<5000;i++){
            list.pollFirst();   //取出列表首元素
            list.pollLast();    //取出列表尾元素
        }
    }

}

/**
 * 
 * @author fcs
 * @date 2015-6-21
 * 描述:创建100个线程添加元素,再创建100个线程取出元素
 * 说明:程序中的集合最终的元素个数是0
 */
public class Main {
    public static void main(String[] args) {
        ConcurrentLinkedDeque<String> list = new ConcurrentLinkedDeque<String>();
        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 threads have been launched\n",threads.length);
        for(int i =0;i< threads.length;i++){
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Main: Size of the list: "+list.size());
        Thread [] threads1 = new Thread[100];

        for(int i =0 ;i<threads1.length;i++){
            PollTask task = new PollTask(list);
            threads1[i] = new Thread(task);
            threads1[i].start();
        }

        System.out.printf("Main: %d PollTask threads have been lanuched\n",threads.length);
        //必须等待线程执行完成,否则会取不完数据,
        for(int i =0 ;i<threads1.length;i++){
            try {
                threads1[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //注意打印出的列表大小可能不是真实的,
        //尤其当有线程在添加数据或者移除数据的时候,这个方法需要遍历整个列表来计算元素量
        System.out.printf("Main: Size of the list : %d\n",list.size());
    }
}

运行效果截图:

java 多线程处理数据 结果放list中 java多线程处理list不重复_线程安全

2.使用一般的集合列表做多线程任务测试

/**
 * 
 * @author fcs
 * @date 2015-6-21
 * 描述:向集合中添加元素,添加10000个
 * 说明:使用一般的List集合装填数据,在多线程下会产生很大的问题
 */
public class AddTask implements Runnable{

    private List<String> list;

    public AddTask(List<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        for(int i =0;i<100;i++){
            list.add(name+": Element "+i);
        }
    }

}

/**
 * 
 * @author fcs
 * @date 2015-6-21
 * 描述:从集合中取出元素
 * 说明:使用一般的List集合获取数据,在多线程并发访问的时候会出现很大的问题
 */
public class PollTask implements Runnable{

    private List<String> list;

    public PollTask(List<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        for(int  i =0;i<100;i++){
            /*list.pollFirst();   //取出列表首元素
            list.pollLast();    //取出列表尾元素
*/  
            list.remove(i);
        }
    }

}

/**
 * 
 * @author fcs
 * @date 2015-6-21
 * 描述:创建100个线程添加元素,再创建100个线程取出元素
 * 说明:
 */
public class Main {
    public static void main(String[] args) {
    //将这里声明改为LinkedList则同样会出现异常,并与ArrayList不同
        List<String> list = new ArrayList<String>();
        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 threads have been launched\n",threads.length);
        for(int i =0;i< threads.length;i++){
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Main: Size of the list: "+list.size());
        //Thread [] threads1 = new Thread[100];

        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 threads have been lanuched\n",threads.length);
        for(int i =0 ;i<threads.length;i++){
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.printf("Main: Size of the list : %d\n",list.size());
    }

}

使用ArrayList实现方式的运行结果:

java 多线程处理数据 结果放list中 java多线程处理list不重复_并发_02


使用LinkedList实现方式的运行结果:

java 多线程处理数据 结果放list中 java多线程处理list不重复_java_03


java 多线程处理数据 结果放list中 java多线程处理list不重复_线程安全_04

本节知识点:

1.使用非阻塞式线程安全列表
java 7引入了ConcurrentLinkedDeque 类来实现非阻塞式并发列表。
所用方法:
pollFirst(): 返回并移除列表中的第一个元素
pollLast():返回并移除列表中的最后一个元素 ,如果列表为空,则这两个方法将返回null
size(): 输出列表中的元素数量,但是这个方法返回的可能不是真实的值,尤其当有线程添加数据或者移除数据时,这个方法需要变量整个列表来计算
元素量,而遍历过的数据可能已经改变,仅当没有任何线程修改列表时,才能保证返回的结果是准确的。
getFirst()/getLast():分别返回列表中第一个和最后一个元素,返回的元素不会从列表中移除,如果列表为空,这两个方法抛出NoSuchElementException异常。
peek()/peekFirst()/peekLast():分别返回列表中第一个或最后一个元素,返回的元素将不会从列表中移除,如果列表为空,这些方法将返回null。
remove()/removeFirst()/removeLast():分别返回列表中第一个和最后一个元素,返回的元素将从列表中移除,如果列表为空,
这些方法将抛出NoSuchElementException异常。
ConcurrentLinkedDeque是JSR166y中新增的一个无界并发Deque实现,基于已链接节点的、任选范围的双端队列。在迭代时,队列保持弱一致性,但不会抛出ConcurrentModificationException异常。
需要小心,与大多数 collection 不同,size 方法不是一个固定时间操作。由于这些队列的异步特性,确定当前元素的数量需要遍历这些元素。
另外,一些批量操作,诸如 addAll, removeAll, retainAll, containsAll, equals, toArray等,不能够保证会立刻执行。比如通过addAll方法批量提交若干元素,于此同时另一线程在迭代时,可能只能访问到先前存在的元素。
内存一致性效果:当存在其他并发 collection 时,将对象放入 ConcurrentLinkedDeque 之前的线程中的操作 happen-before 随后通过另一线程从 ConcurrentLinkedDeque 访问或移除该元素的操作。
先前存在一个线程安全并阻塞的LinkedBlockingDeque实现,现在好了,又多了个并发实现,这样和Queue保持一致,并发和阻塞版本都具有了。嗯,果然是好事要成双。
在使用上没有什么可说的,随时查看API DOC,即可。刚开始以为Fork/Join的工作窃取(work stealing)机制内部使用ConcurrentLinkedDeque实现,查看ForkJoinPool源代码时,不曾发现其身影。(本段引用网上资料)