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(); } } }