这个是在学习工作中的一些总结,若有不对之处欢迎大家指出。侵删!
需要源码联系QQ:1352057131
得之在俄顷,积之在平日。
目录
并发容器概览
古老和过时的同步容器
Vector
Hashtable
ConcurrentHashMap
Map接口图示
HashMap结构
HashMap不安全的原因
同时put碰撞导致数据丢失
同时put扩容导致数据丢失
死循环造成的cpu100%(仅在1.8版本之前出现)
ConcurrentHashMap结构
JDK1.7:
ConcurrentHashMap常见用法
CopyOnWriteArrayList
背景
适用场景
读写规则
实现原理
缺点
并发队列Queue
为什么需要使用队列
队列关系图
阻塞队列BlockingQueue
简介
常用方法
ArrayBlockingQueue示例
LinkedBlockingQueue
PriorityBlockingQueue
非阻塞并发队列
ConcurrentHashMap:线程安全的HashMap
CopyOnWriteArrayList:线程安全的List
BlockingQueue:这是一个借口,表示阻塞队列,非常适用于作为数据共享的通道
ConcurrentLinkQueue:高效的非阻塞并发队列,使用链表实现,可以看作是一个线程安全的LinkList
ConcurrentSkipListMap:是一个map,使用跳表的数据结构进行快速查找
Vector可以看成是一个线程安全的list,因为底层的方法是用synchronized进行修饰了的,因此在并发的情况下执行效率并不高
Hashtable可以看成是一个线程安全的HashMap,因为底层的方法是用synchronized进行修饰了的,因此在并发的情况下执行效率并不高
JDK1.7
JDK1.8
多个线程如果同时put,而且key计算出来的hashCode都相同,则会放入同一个位置,因此造成数据丢失
如果多个线程同时扩容,那么只会有一个扩容后的数组会被保留下来
原因:多个线程同时扩容的时候会造成链表的死循环,也就是你指向我,我指向你。
具体参见:https://www.jianshu.com/p/619a8efcf589
Jdk1.7中的ConcurrentHashMap最外层是多个segment,每个segment底层数据结构和HashMap相似,仍然是数组和链表组成的拉链法。
每个segment独立上ReentrantLock锁,每个segment之间互不影响,提高了并发效率。
ConcurrentHashMap默认有16个segment,所以最多同时支持16个线程并发写(操作分别在不同的segment上)该默认值可以在初始化的时候指定,一旦指定就无法扩容。
JDK1.8
从图中我们可以看出ConcurrentHashMap的结构和HashMap结构非常相似,1.8中ConcurrentHashMap最外层不再采用segment,而是采用node节点,保证线程安全的方式不再是ReentrantLock而是CAS+Synchronized
java诞生之初 就有线程安全的Vector,但Vector对读写都是通过synchronized关键字来同步的,性能并不好且Vector每次扩容是原来的1倍,存在内存浪费的可能。Vector和SynchronizeList锁的粒度较大,并发时效率相对较低,并且迭代时无法编辑。对于线程安全的List JDK提供了CopyOnWriteArrayList
读完全不用加锁,写入也不会阻塞读操作,只有写入与写入之间需要进行同步等待。
读操作是无锁的,性能较高。写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。如下图所示:
用队列可以在线程间传递数据:生产者消费者模式、银行转账。
考虑到锁等线程安全问题的重任从“你”转移到了“队列”上。
阻塞队列是具有阻塞功能的队列,阻塞队列的一端给生产者用,另一端给消费者使用,阻塞队列是线程安全的。
放入数据
获取数据
是否有界
是否有界即容量有多大,无界队列的参数是Integer.MAX_VALUE。
public class ArrayBlockingQueueTest {
/*模拟一个面试官,10名面试者,而只有两把椅子给面试者坐*/
//创建一个容量为2的队列
private final static ArrayBlockingQueue
public static void main(String[] args) {
Interviewer interviewer = new Interviewer(abq);
Interviewee interviewee = new Interviewee(abq);
new Thread(interviewee).start();
new Thread(interviewer).start();
}
}
//面试官
class Interviewer implements Runnable{
private ArrayBlockingQueue
public Interviewer(ArrayBlockingQueue arrayBlockingQueue) {
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
while(!arrayBlockingQueue.isEmpty()) {
String name = arrayBlockingQueue.poll();
System.out.println("正在面试"+name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"面试完毕");
}
System.out.println("无面试者");
}
}
//面试者
class Interviewee implements Runnable{
private ArrayBlockingQueue
public Interviewee(ArrayBlockingQueue
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
//将8个面试者放入队列
System.out.println("所有的面试者都到齐了");
for (int i = 0; i < 8; i++) {
try {
arrayBlockingQueue.put("面试者"+i);
System.out.println("面试者"+i+"进入队列等待");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
该队列是一个无界队列,容量为Integer.MAX_VALUE,内部结构是node节点和两把锁:
正在上传…重新上传取消转存失败重新上传取消
这样使用两把锁的好处是让take和put互不干扰
这是一个支持优先级的无界队列,并且可以自然排序。它是PriorityQueue的线程安全版本。
SynchronousQueue
这是一个容量为0的队列,因此都是直接传递,没有peek、iterate等方法,从而效率很高。这是一个非常好的用来直接传递的并发数据结构;SynchronousQueue是线程池Executors.newCachedThreadPool()
使用的阻塞队列。
并发包中非阻塞队列只有ConcurrentLinkedQueue,它是使用链表作为其数据结构,使用CAS非阻塞算法来实现线程安全,适用于对性能要求高的并发场景。