Java多线程——安全线程问题(ConcerrentHashMap、CopyOnWriteArrayList 、CopyOnWriteArraySet、ArrayBlockingQueue)
线程安全的集合
问题的由来:
1 import java.util.ArrayList; 2 3 4 5 public class Demo05 { 6 7 public static void main(String[] args) { 8 9 // 创建ArrayList 10 11 ArrayList<String> arrayList=new ArrayList<>(); 12 13 // 创建线程 14 15 for (int i=0;i<=10;i++){ 16 17 int temp = i; 18 19 new Thread(new Runnable() { 20 21 @Override 22 23 public void run() { 24 25 for (int j = 0;j<=10;j++){ 26 27 arrayList.add(Thread.currentThread().getName()+"==="+temp+"==="+j); 28 29 System.out.println(arrayList.toString()); 30 31 } 32 33 } 34 35 }).start(); 36 37 } 38 39 40 41 } 42 43 }
问题:并发异常。
Collections中的工具方法
JDK1.2提供,接口统一、维护性高,性能未提升,仍是用synchronized实现
import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class Demo05 { public static void main(String[] args) { //// 方法一 、创建ArrayList // ArrayList<String> arrayList=new ArrayList<>(); //// 使用Collections中的线程安全方法转化为线程安全的集合的 // 方法二 、 List<String> synList= Collections.synchronizedList(arrayList); CopyOnWriteArrayList<String> arrayList=new CopyOnWriteArrayList<>(); // 创建线程 for (int i=0;i<=10;i++){ int temp = i; new Thread(new Runnable() { @Override public void run() { for (int j = 0;j<=10;j++){ arrayList.add(Thread.currentThread().getName()+"==="+temp+"==="+j); System.out.println(arrayList.toString()); } } }).start(); } } }
CopyOnWriteArrayList集合
线程安全的ArrayList集合,加强版的读写分离
写有锁,读无锁,读写之间不阻塞,优于读写锁
写入时,先复制一个容器副本,再添加新的元素,最后替换引用
(简单来说就是牺牲内存空间来换取安全性)
使用方法与ArrayList无差异
import java.util.Random; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo06 { public static void main(String[] args) { // 1.创建集合 CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>(); // 2.使用多线程操作 ExecutorService es = Executors.newFixedThreadPool(5); // 3.提交任务 for (int i=1;i<=5;i++){ es.submit(new Runnable() { @Override public void run() { list.add(Thread.currentThread().getName()+"..."+new Random().nextInt(1000)); } }); } // 4.关闭线程池 es.shutdown(); while(!es.isTerminated()){ } // 5.打印结果 System.out.println("元素个数:"+list.size()); for (String s : list){ System.out.println(s); } } }
CopyOnWriteArraySet
底层的实现逻辑为CopyOnWriteArrayList,但是添加元素为addIfAbsent()方法,会遍历数组
如存在元素,则不添加,扔掉副本。
重复依据,copyOf()方法。
代码示例:
import java.util.concurrent.CopyOnWriteArraySet; public class Demo07 { public static void main(String[] args) { CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet<>(); // 2.添加元素 set.add("pingguo"); set.add("huawei"); set.add("xiaomi"); // 3.打印 System.out.println("元素个数:"+set.size()); System.out.println(set.toString()); } }
Queue(Collection的子接口)队列
表示FIFO(FIRST IN FIRST OUT)先进先出
常用方法
Boolean offer(E e) 顺序添加一个元素(达到上限后再添加则返回false)
E poll() 获得第一个元素并移除(若无元素返回null)
E peak() 获得第一个元素但不移除(若无元素返回null)
代码示例:
import java.sql.SQLOutput; import java.util.LinkedList; import java.util.Queue; public class Demo08 { public static void main(String[] args) { // 1.创建队列 Queue<String> queue=new LinkedList<>(); // 2.入队 queue.offer("苹果"); queue.offer("西瓜"); queue.offer("榴莲"); // 3.出队 System.out.println(queue.peek()); System.out.println("========"); System.out.println("元素个数:"+queue.size()); int size=queue.size(); for (int i=0;i<=3;i++){ System.out.println(queue.poll()); } System.out.println("出对完毕:"+queue.size()); } }
注意:以上代码是由单线程的集合实现Linklist,非安全线程。
Queue相关的安全线程
ConcurrentLinkedQueue(无界队列)
线程安全、可读写的队列,高并发下性能最好的队列
无锁、CAS比较交换算法,修改方法包含三个关键参数<V,E,N>
其中V是读取出来的量,即要更新的变量;E判断原值和读取出来的值是一样的以确保在读取过程中不会被修改;V,新值。只有V==E,V=N.否则表示已经更新过,取消当前操作。
代码示例:
import java.util.concurrent.ConcurrentLinkedQueue; public class Demo09 { public static void main(String[] args) throws Exception{ // 1.创建安全队列 ConcurrentLinkedQueue<Integer> queue=new ConcurrentLinkedQueue<>(); // 2.入队操作 Thread thread=new Thread(new Runnable() { @Override public void run() { for (int i=0;i<=5;i++){ queue.offer(i); } } }); Thread thread2=new Thread(new Runnable() { @Override public void run() { for (int i=6;i<=10;i++){ queue.offer(i); } } }); // 3.启动线程 thread.start(); thread2.start(); thread.join(); thread2.join(); // 4.出队操作 int j=queue.size(); for (int i=0;i<=j;i++){ System.out.println(queue.poll()); } } }
BlockingQueue
Queue的子接口,阻塞的队列增加两个线程状态为无限期等待的方法。
Void put(E e)//将指定元素插入此队列中,如果没有可用空间,则等待
E take()//获取并移除此队列头部元素,如果没有则等待
(可解决线程中类似的生产者消费者问题)
代码示例:
import java.util.concurrent.ArrayBlockingQueue; public class Demo10 { public static void main(String[] args) throws Exception{ ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(5); queue.put("a"); queue.put("b"); queue.put("c"); queue.put("d"); queue.put("f"); System.out.println("已添加五个元素"); // ArrayBlockingQueue,只能容纳五个元素,再添加元素则阻塞 // 若移除其中一个元素则可以继续往下执行 queue.take(); queue.put("h"); System.out.println("添加第六个元素"); // System.out.println(queue.toString()); } }
再次实现生产者消费者的问题:
import java.util.concurrent.ArrayBlockingQueue; public class Demo11 { public static void main(String[] args) { // 1.创建队列 ArrayBlockingQueue<Integer> abq=new ArrayBlockingQueue<>(6); // 2.创建两个线程 Thread t1=new Thread(new Runnable() { @Override public void run() { for (int i=0;i<=30;i++){ try { abq.put(i); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"生产了第"+i+"号面包"); } } },"ll"); Thread t2=new Thread(new Runnable() { @Override public void run() { for (int i=0;i<=30;i++){ try { Integer num=abq.take(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"消费了第"+i+"号面包"); } } },"zz"); // 启动线程 t1.start(); t2.start(); } }
ConcurrentHashMap
JDK1.7以及之前,初始容量为十六段,最大并发量也是16,枷锁方式为对每一个小段加锁,当两个或更多的对象加入到同一个Segment时需要HashMap。使用方式与HashMap无差异。JDK1.8之后使用的是无锁算法即CAS.效率更高。
代码示例:
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput; import java.util.concurrent.ConcurrentHashMap; public class Demo12 { public static void main(String[] args) { // 1.创建集合 ConcurrentHashMap<String,String> hashMap=new ConcurrentHashMap<String,String>(); // 2.使用多线程添加数据 for (int i=0;i<=5;i++){ new Thread(new Runnable() { @Override public void run() { for (int k=0;k<=10;k++){ hashMap.put(Thread.currentThread().getName()+"---"+k,k+"<-"); System.out.println(hashMap.toString()); } } }).start(); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术