Java并发容器
并发容器概览
- ConcurrentHashMap:线程安全的HashMap
- CopyOnWriteArrayList:线程安全的List
- BlockingQueue:这是一个接口,表示阻塞队列,非常适合用作数据共享的通道
- ConcurrentLinkedQueue:高效的非阻塞并发队列,使用链表实现。可以看做一个线程安全的LinkedList。
集合类的历史
Vector和Hashtable
Vector中方法均被synchronized修饰,所以并发性能很差
Hashtable可以理解为线程安全的HashMap,很多方法也是被synchronized修饰的,所以并发性能也很差。
ArrayList和HashMap
这两个类是线程不安全的,可以使用Collections.synchronizedList(new ArrayList())和Collections.synchronizedMap(new HashMap<K,V>())使之变成线程安全的。这种策略和Vector和Hashtable几乎相同
ConcurrentHashMap和CopyOnWriteArrayList
- 取代同步的HashMap和同步的ArrayList
- 绝大多数情况下,ConcurrentHashMap和CopyOnWriteArrayList的性能都更好。
ConcurrentHashMap(重点)
HashMap
为什么HashMap是线程不安全的
- 同时put碰撞导致数据丢失
- 同时put扩容导致数据丢失
- 死循环造成CPU100%
HashMap并发的特点
- 非线程安全
- 迭代时不允许修改内容
- 只读的并发是安全的
- 若一定要把HashMap用在并发环境,使用Collection.synchronizedMap(new HashMap()).
ConcurrentHashMap
在JDK1.7中的ConcurrentHashMap最外层是多个segment,每个segment的底层数据结构与HashMap类似,仍然是数组和链表组成的拉链法,每个segment独立上ReentrantLock锁,每个segment之间互不影响,提高了并发效率。
在JDK1.8中借鉴了HashMap的设计,采用拉链法。拉链由红黑树和链表共同组成。
源码分析
//put方法,和HashMap的put方法基本相同,使用synchronized+cas实现线程安全
final V putVal(K key, V value,boolean onlyIfAbsent){
if(key == null || value == null) throw new NullPointerException();
}
ConcurrentHashMap在JDK1.7与8中的区别
https://www.jianshu.com/p/e694f1e868ec
https://www.cnblogs.com/lezon1995/p/11219555.html
concurrentHashMap的replace方法
public class OptionsNotSafe implements Runnable{
private static ConcurrentHashMap<String,Integer> scores = new ConcurrentHashMap<String,Integer>();
public static void main(String[] args) throws InterruptedException{
scores.put("小明",0);
Thread t1 = new Thread(new OptionsNotSafe());
Thread t2 = new Thread(new OptionsNotSafe());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(scores);
}
@Override
public void run(){
for(int i = 0; i < 1000;i++){
while(true){
Integer score = scores.get("小明");
Integer newScore = score+1;
boolean b = scores.replace("小明",score,newScore);
if(b == true)
break;
}
}
}
}
CopyOnWriteArrayList
- 诞生的历史原因
- 适用场景
- 读写规则
- 实现原理
- 缺点
- 源码分析
主要用于代替Vector和SynchronizedList,就和ConcurrentHashMap代替SynchronizedMap的原因一样,Vector和SynchronizedList的锁的粒度太大,并发效率相对较低,并且迭代时无法编辑。CopyOnWrite并发容器还包括CopyOnWriteArraySet,用来代替同步Set。
读操作可以尽可能快,而写即使慢一点也没关系。
CopyOnWriteArrayList使用场景
读多写少:黑名单,每日更新:监听器:迭代操作远多于修改操作。
CopyOnWriteArrayList读写规则
读写锁中,读读共享,其他互斥
CopyOnWriteArrayList读写规则的升级:读取时完全不加锁,并且更厉害的时,写入也不会阻塞读取操作,只有写入和写入之间需要进行同步等待。
public class CopyOnWriteArrayListDemo {
public static void main(String[] args) {
//ArrayList<String> list = new ArrayList<>();
CopyOnWriteArrayList list = new CopyOnWriteArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println("list is" + list);
String next = iterator.next();
System.out.println(next);
if(next.equals("2")){
list.remove("5");
}
}
}
}
CopyOnWriteArrayList实现原理
不对内存直接进行修改,把原始内容放到新的内容中拷贝出来,在新的地方添加删除,在新的地方写入,在原始数据删除。
创建新副本,读写分离。
"不可变"原理
迭代的时候
public class CopyOnWriteArraListDemo2 {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(new Integer[]{1,2,3});
System.out.println(list);
Iterator<Integer> itr1 = list.iterator();
list.add(4);
System.out.println(list);
Iterator<Integer> itr2 = list.iterator();
itr1.forEachRemaining(System.out::println);
itr2.forEachRemaining(System.out::println);
}
}
CopyOnWriteArrayList缺点
数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的数据,马上就能读到,就不要适用CopyOnWrite容器。
内存占用问题,因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个内存的占用。
public class CopyOnWriteArraListDemo2 {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(new Integer[]{1,2,3});
System.out.println(list);
Iterator<Integer> itr1 = list.iterator();
list.add(4);
System.out.println(list);
Iterator<Integer> itr2 = list.iterator();
itr1.forEachRemaining(System.out::println);
itr2.forEachRemaining(System.out::println);
}
}
CopyOnWriteArrayList源码分析
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
}
public E get(int index) {
return elementAt(getArray(), index);
}
static <E> E elementAt(Object[] a, int index) {
return (E) a[index];
}
并发队列
- 为什么要用队列
- 并发队列简介
- 各并发队列关系图
- 阻塞队列BlockingQueue
- 非阻塞队列
- 如何选择适合自己的队列
为什么要适用队列
- 队列可以在线程间传递数据:生产者消费者模式,银行转账
- 考虑锁等线程安全问题的重任从你转移到了队列上
并发队列简介
BlockingQueue
- put,take
- add,remove,element
- offer,pool,peek
ArrayBlockingQueue
使用案例,生产者消费者模式
public class ArrayBlockQueueDemo {
static BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
public static void main(String[] args) {
Producer r1 = new Producer(queue);
Consumer r2 = new Consumer(queue);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
class Producer implements Runnable{
BlockingQueue<String> queue;
public Producer(BlockingQueue blockingQueue){
this.queue = blockingQueue;
}
@Override
public void run() {
for(int i = 0; i < 10; i++){
System.out.println("我生成了"+ i);
queue.offer("Consumer"+i);
}
queue.offer("stop");
}
}
class Consumer implements Runnable{
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
public Consumer(BlockingQueue blockingQueue){
this.queue = blockingQueue;
}
@Override
public void run() {
String msg="";
while(!msg.equals("stop")){
try {
msg = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(msg);
}
}
}
BlockQueue有add,offer,put3个方法,下面讲述3个方法的不同
add方法调用offer方法。
public boolean offer(E e) {
//检查元素不为null
Objects.requireNonNull(e);
//加锁,独占锁保护静态资源
final ReentrantLock lock = this.lock;
lock.lock();
try {
//队列已满,返回false
if (count == items.length)
return false;
else {
//插入元素返回true
enqueue(e);
return true;
}
} finally {
//释放锁
lock.unlock();
}
}
public void put(E e) throws InterruptedException {
//同上
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
//当队列满是,陷入阻塞。
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
弹出元素有两个
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
LinkedBlockingQueue
容量是无界的,容量时Integer.MAX_VALUE,Node是链表中的节点
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
//用于唤醒一个被wait的线程
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
PriorityBlockQueue
支持优先级,自然顺序,而不是先进先出。PriorityQueue是无界的
SynchronousQueue
- SynchronousQueue没有peek等函数,因为peek的含义是取出头节点,但是SynchronousQueue的容量是0,所以连头节点都没有,也就没有peek方法。同理,没有iterate相关方法。是一个极好的用来直接传递的并发数据结构。
- SynchronousQueue是线程池Executors.newCachedThreadPool()使用的阻塞队列。
DelayQueue
非阻塞并发队列
ConcurrentLinkedQueue