Java--线程安全的集合

一、线程安全集合

  Collection体系:

    List集合:CopyOnWriteArrayList<Class>(线程安全集合) 

    Set集合:CopyOnWriteArraySet<Class> (线程安全集合)

    新增Queue(interface)(队列:先进先出的结构):

      BlocingQueue(interface) :ArrayBlockingQueue<Class> (线程安全集合)

                  LinkedBlockingQueue<Class> (线程安全集合)

      ConcurrentLinkedQueue<Class>

  Map体系:ConcurrentSkipListMap、ConcurrentHashMap

  Collections工具类中提供的可以获得线程安全集合的方法:

    • public static <T> Collection <T> synchronizedCollection(Collection<T> c)
    • public static <T> List <T> synchronizedList(List<T> List)
    • public static <T> Set<T> synchronizedSet(List<T> s)
    • public static <K,V> Map <K,V> synchronizedMap(Map<K,V> m)
    • public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s) 
    • public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)

  注:JDK1.2提供,接口统一、维护性高,但性能没有提升,均以synchonized实现。

    JDK1.5之后 直接使用线程安全集合就可以了

  实例:使用多线程操作线程不安全集合

  注:1.使用Collections中的线程安全方法转换为线程安全的集合: Collections.synchroniedList();

    2.使用CopyOnWriteArrayList线程安全的集合

package com.monv.chatper14_1;
/**
 * 使用多线程操作线程不安全集合
 */
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class ArrayListDemo {
    public static void main(String[] args) {
        //1.创建arrayList 线程不安全的集合 报错 并发修改异常(ConcurrentModificationExecption)
        //ArrayList<String> arrayList = new ArrayList<>();
        //1.1使用Collections中的线程安全方法转换成线程安全的集合
        //List<String> synList = Collections.synchronizedList(arrayList);
        //1.2使用CopyOnWriteArrayList
        CopyOnWriteArrayList<String> arrayList2 = new CopyOnWriteArrayList<>();
        //2.创建线程 20 个
        for (int i =0;i<20;i++) {
            int num = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int j = 0;j<10;j++) {
                        arrayList2.add(Thread.currentThread().getName()+"===="+num+"====="+j);
                        System.out.println(arrayList2.toString());
                    }
                }
            }).start();
        }
        
    }
}

二、CopyOnWriteArrayList

  线程安全的ArrayList,加强版的读写分离。

  写有锁、读无锁,读写之间不阻塞,优于读写锁(ReentrantReadWriteLock)

  写入时,先Copy一个容器副本、再添加新元素,最后替换引用。(缺点:比较占空间)

  使用方式与ArrayList无异。

  案例:使用多线程来操作CopyOnWriteArrayList

package com.monv.chatper14_1;

import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 使用多线程操作CopyOnWriteArrayList
 * @author Monv
 *
 */
public class CopyArrayList {
    public static void main(String[] args) {
        //1.创建集合
        CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>();
        //2.使用多线程
        ExecutorService es= Executors.newFixedThreadPool(5);
        //3.提交任务
        for(int i = 0 ;i<5;i++) {
            es.submit(new Runnable() {
                
                @Override
                public void run() {
                    for(int j = 0;j<10;j++) {
                        arrayList.add(Thread.currentThread().getName()+"-----"+new Random().nextInt(1000));
                    }
                }
            });
        }
        //4.关闭线程池
        es.shutdown();
        while(!es.isTerminated()) {
            
        }
        //5.打印
        System.out.println("元素个数:"+arrayList.size());
        for (String string : arrayList) {
            System.out.println(string);
        }
    }
}

三、CopyOnWriteArraySet

  线程安全的Set,底层使用CopyOnWriteArrayList

  唯一不同在于,使用addIfAbsent()添加元素,会遍历数组

  如果元素存在,则不添加(扔掉副本) 判断存在的依据 用 equals()方法

package com.monv.chatper14_1;

import java.util.concurrent.CopyOnWriteArraySet;

/**
 * 演示CopyOnWriteArraySet 
 * 这个是有序的
 * 往CopyOnWriteArraySet里边添加数据的时候 实际上就是往 CopyOnWriteArrayList 中添加的数据
 * 判断的依据 equals
 * @author Monv
 *
 */
public class CopyArraySet {
    public static void main(String[] args) {
        //1.创建集合
        CopyOnWriteArraySet<String> arraySet = new CopyOnWriteArraySet<>();
        //2.添加元素
        arraySet.add("苹果");
        arraySet.add("华为");
        arraySet.add("小米");
        arraySet.add("VIVO");
        arraySet.add("苹果");//这个不会再添加了
        //3.打印
        System.out.println("元素个数:"+arraySet.size());
        System.out.println(arraySet.toString());
    }
}

四、Queue接口(队列)

  Collection的子接口,表示队列FIFO(First In First Out) 先进先出

  常用方法:

    抛出异常:

      boolean add(E e) //顺序添加一个元素(到达上限后,再添加则会抛出异常)

      E remove() //获得第一个元素并移除(如果队列没有元素时,则抛出异常)

      E element() //获得第一个元素但不移除(如果队列没有元素时,则抛异常)  

    返回特殊值:推荐使用

      boolean offer(E e) //顺序添加一个元素(到达上限后,再添加则会返回false)

      E poll() //获得第一个元素并移除(如果队列没有元素时,则返回Null)

      E peek() //获得第一个元素但不移除(如果队列没有元素时,则返回Null)

  案例:Queue使用

package com.monv.chatper14_1;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 队列 先进先出
 * Queue接口的使用
 * @author Monv
 *
 */
public class QueueDemo {
    public static void main(String[] args) {
        //1.创建队列  LinkedList这个是线程不安全的 只能用单线程
        Queue<String> queue = new LinkedList<String>();
        //2.入队
        queue.offer("苹果");
        queue.offer("橘子");
        queue.offer("葡萄");
        queue.offer("香蕉");
        queue.offer("草莓");
        //3.出队
        System.out.println(queue.peek());//返回队列的第一个元素 不会删除
        
        System.out.println("元素个数:"+queue.size());
        int count = queue.size();
        for (int i = 0;i<count;i++) {
            System.out.println(queue.poll());
        }
        System.out.println("出队完毕,元素个数:"+queue.size());
        
    }
}

  ConcurrentLinkedQueue:线程安全队列

    线程安全、可高效读写的队列,高并发下性能最好的队列

    无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)

    V:要更新的变量 E:预期值 N:新值

    当V==E时,V才更新为N;否则表示已经被更新过,则取消当前操作。

    修改值的步骤:1.V是要修改的值

           2.从V里边读出一份数据 放到E

           3.把N更新到V时,判断V和E是否相等,如果不相等,则说明V的数据改变了,不更新;如果相等,则说明V的数据没有修改,更新成功。

  案例:ConcurrentLinkedQueue的使用

package com.monv.chatper14_1;

import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * ConcurrentLinkedQueue的使用
 * @author Monv
 *
 */
public class ConLinkedQueueDemo {
    public static void main(String[] args) throws Exception {
        //1.创建安全队列
        ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
        //2.入队操作
        Thread t1 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                for(int i =1 ;i<=5;i++) {
                    queue.offer(i);
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                for(int i = 6 ;i<=10;i++) {
                    queue.offer(i);
                }
            }
        });
        //3.启动
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        //4.出队操作
        System.out.println("----出队----");
        int size = queue.size();
        for (int i =0;i<size;i++) {
            System.out.println(queue.poll());
        }
    }
}

  BlockingQueue(阻塞队列)

    Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法

    方法:

      void put(E e)// 将指定元素插入此队列中,如果没有可用空间,则等待

      E take()//获取并移除此队列头部元素,如果没有可用元素,则等待

    ArrayBlockingQueue:数组结构实现,有界队列。(手工固定上限)

    LinkedBlockingQueue:链表结构实现,有界队列。(默认上限Integer.MAX_VALUE)

package com.monv.chatper14_1;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * 阻塞队里的使用
 * 实例1.创建一个有界队列,添加数据
 * 实例2.使用阻塞队列实现生产者和消费者
 * @author Monv
 */
public class ArrayBlockQueueDemo {
    public static void main(String[] args) {
        //1.创建一个有界队列,添加数据
        ArrayBlockingQueue<String > abq = new ArrayBlockingQueue<>(5);
    
        try {
            //添加元素
            abq.put("aaa");
            abq.put("bbb");
            abq.put("ccc");
            abq.put("ddd");
            abq.put("eee");
            //删除元素
            abq.take();
            System.out.println("已经添加了5个元素");
            abq.put("xyz");
            System.out.println("已经添加了6个元素");
            System.out.println(abq.toString());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //案例2 使用阻塞队列实现生产者和消费者
        //1.创建一个队列
        ArrayBlockingQueue<Integer> abq1 = new ArrayBlockingQueue<>(6);
        //2. 创建2个线程
        Thread t1 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                for(int i =0;i<30;i++) {
                    try {
                        abq1.put(i);
                        System.out.println(Thread.currentThread().getName()+"生产了第"+i+"个面包");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }, "晨晨");
        Thread t2 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                for(int i =0;i<30;i++) {
                    try {
                        Integer num = abq1.take();
                        System.out.println(Thread.currentThread().getName()+"消费了第"+num+"个面包");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }, "冰冰");
        
        t1.start();
        t2.start();
    }
}

 五、ConcurrentHashMap

  初始容量默认为16段(Segment),使用分段锁设计(0-15段 ,每段都是独立的哈希表)

  不对整个Map加锁,而是为每个Segment加锁。

  当多个对象存入同个Segment时,才需要互斥。

  最理想的状态为16个对象分别存入16个Segment,并行数量16.

  使用方式与HashMap无异。

  注:JDK1.7之前采用分段锁的设计方式,JDK1.8之后改为CAS无锁算法,比分段锁的设计方式效率要高

package com.monv.chatper14_1;

import java.util.concurrent.ConcurrentHashMap;

public class ConHashMapDemo {
    public static void main(String[] args) {
        //创建集合
        ConcurrentHashMap<String,String> cHashMap = new ConcurrentHashMap<>();
        //使用多线程添加元素
        for(int i =0;i<5;i++) {
            new Thread(new Runnable() {
                
                @Override
                public void run() {
                    for (int j=0;j<10;j++) {
                        cHashMap.put(Thread.currentThread().getName()+j+"==",j+"");
                        System.out.println(cHashMap);
                    }
                }
            }).start();
        }
    }
}

 

posted @ 2021-01-23 18:11  改Bug的小魔女  阅读(231)  评论(0编辑  收藏  举报