线程安全的集合容器
List集合中的线程安全的集合容器:
在旧版本中Vector是线程安全的集合容器,在JDK 1.5以后CopyOnWriteArrayList也是线程安全的集合容器,CopyOnWriteArrayList的数据结构是Object类型的数组。
CopyOnWriteArrayList是如何实现线程安全的:通过 “ReentrantLock锁” 实现线程安全,写入操作(添加、删除、修改元素)使用同一个Lock对象进行线程安全的同步管理,采用COW思想(CopyOnWrite);因为CopyOnWriteArrayList集合在扩容时实在原数组基础上加一进行扩容,就是添加一个元素扩容一下,当它进行扩容时,会将原数组进行复制到一个新数组中进行添加元素的操作,所以不会影响集合的读取;读写分离:线程读取集合元素的同时,其它线程可以同时写入
Set集合中的线程安全的集合容器:
CopyOnWriteArraySet: CopyOnWriteArraySet的底层就是CopyOnWriteArrayList,但在此基础上添加了去重方法
Queue(队列):
队列的特点:FIFO先进先出
BlockingQueue阻塞队列:
特点:在队列的基础上,使用两个不同的线程进行队列操作
- 读取线程负责读取队列元素,进行出队操作,当队列为空时,读取线程阻塞;发生出队操作后,唤醒阻塞中的写入线程;
- 写入线程负责写入队列元素,进行入队操作,当队列为满时,写入线程阻塞;发生入队操作后,唤醒阻塞中的读取线程;
实现类
-
有界队列:ArrayBlockingQueue
数据结构:Object类型的数组
因为ArrayBlockingQueue是有Object数组实现的并且在ArrayBlockingQueue只有有参构造方法,参数是队列的大小
线程的同步:通过一个ReentrantLock锁实现(写入的同时,不允许读取)
通过观察源码可以得知:在ArrayBlockingQueue的put()方法中,当队列的元素个数等于数组长度会使put线程进入阻塞状态(并不是真正意义上的阻塞相当于等待状态)小案例:
package Threadday4; import java.util.concurrent.ArrayBlockingQueue; public class Test04 { public static void main(String[] args) throws InterruptedException { // 基于Object数组实现的有界阻塞队列 ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<String>(5); for (int i = 1; i <= 100; i++) { //入队操作可以使用put方法 // arrayBlockingQueue.offer("A" + i); arrayBlockingQueue.put("S" + i); if (i % 5 == 0) { //出队操作可以使用take方法 // System.out.println("出队元素:"+arrayBlockingQueue.poll()); System.out.println("出队元素:"+arrayBlockingQueue.take()); } System.out.println(arrayBlockingQueue); } } }
-
无界队列:LinkedBlockingQueue
数据结构:单向链表
无界队列的无界只是相对意义上的无界
线程同步:通过两个ReentrantLock锁实现(写入操作使用putLock,读取操作使用takeLock)
小案例:
package Threadday4; import java.util.concurrent.LinkedBlockingQueue; public class Test05 { public static <E> void main(String[] args) throws InterruptedException { // 基于单项列表实现的无届队列(无界) // 它的线程同步是通过两个ReentrantLock锁实现的 LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<String>(); for (int i = 1; i <= 100; i++) { //入队操作可以使用put方法 queue.put("S" + i); if (i % 5 == 0) { //出队操作可以使用take方法 System.out.println("出队元素:"+queue.take()); } System.out.println(queue); } } }
-
延迟队列:DelayedWorkQueue
队列中的元素按照延迟时间排序
-
优先队列:PriorityBlockingQueue
队列中的元素按照指定的优先级排序
小案例:使用一个简单的订单说明
OrederPayMent类
package Threadday4; import java.math.BigDecimal; import java.util.UUID; public class OrederPayMent implements Runnable ,Comparable<OrederPayMent>{ private int orderNo; // 订单编号 private String trackId; // 支付流水号 private BigDecimal payment; // 支付金额 public OrederPayMent(int orderNo, BigDecimal payment) { trackId = UUID.randomUUID().toString().substring(0, 10) + System.currentTimeMillis(); this.orderNo = orderNo; this.payment = payment; } public int getOrderNo() { return orderNo; } public void setOrderNo(int orderNo) { this.orderNo = orderNo; } public String getTrackId() { return trackId; } public void setTrackId(String trackId) { this.trackId = trackId; } public BigDecimal getPayment() { return payment; } public void setPayment(BigDecimal payment) { this.payment = payment; } @Override public void run() { System.out.printf("订单编号为:%d的订单,完成支付,支付金额为:¥%s,支付流水号为:%s\n", orderNo, payment, trackId); } @Override public int compareTo(OrederPayMent o) { return o.payment.compareTo(this.payment); } }
根据自定义的优先级规则(价钱)实现Comparable接口
package Threadday4; import java.math.BigDecimal; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Test06 { public static void main(String[] args) { //创建线程池 ThreadPoolExecutor pool = new ThreadPoolExecutor(0, 2, 0, TimeUnit.SECONDS, new PriorityBlockingQueue<Runnable>()); for(int i = 0; i<=100;i++) { pool.execute(new OrederPayMent(i, new BigDecimal(String.valueOf(Math.random()*100)))); } } }
Map集合中线程安全的集合容器:
Hashtable:数据结构:数组加链表因为hashtable的方法中是为synchronized关键字锁修饰的所有线程安全但是性能差
ConcurrentHashMap:
数据结构:Node[ ]数组 + 链表 + 红黑树
在JDK1.7版本中,ConcurrentHashMap是通过分段锁来实现线程安全的,分段锁:是将数组划分成若干不同的"区域",每个区域使用一把锁来控制线程安全;在JDK1.8版本以后,ConcurrentHashMap是通过synchronized同步锁 + CAS来实现线程安全的,就是在多线程并发时,产生Hash冲突,必须竞争同一把锁(链表的头节点或红黑树的根节点)
]数组 + 链表 + 红黑树
在JDK1.7版本中,ConcurrentHashMap是通过分段锁来实现线程安全的,分段锁:是将数组划分成若干不同的"区域",每个区域使用一把锁来控制线程安全;在JDK1.8版本以后,ConcurrentHashMap是通过synchronized同步锁 + CAS来实现线程安全的,就是在多线程并发时,产生Hash冲突,必须竞争同一把锁(链表的头节点或红黑树的根节点)