6. Java并发容器和框架
1. ConcurrentHashMap
高效的线程安全的hash表,HashTable实现的是表锁,而concurrentHashMap实现的是分段所,意味着同一时间可以有多个线程操作Hash表,提高了效率。
Segment继承了Lock,是分段式锁,一个Segment包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,一个Segment守护着一个HashEntry数组中的元素,对HashEntry数组中元素修改时,必须先获得与他对应的Segment锁。
segments默认长度16,每个segment负载因子0.75,cap(每个segment长度)为1。
-
get():不会对段加锁,因为value是用volatile来修饰的,保证了可见性,提高了效率。只有写才会进行加锁操作。
-
put():插入操作必须加锁,然后第一步是判断是否对HashEntry进行扩容,第二步是添加元素。
ConcurrentHashMap扩容机制:
插入元素前判断是否需要扩容,而HashMap是插入元素后再判断是否扩容。ConcurrentHashMap具有的优势就是避免了最后添加元素时进行了无用的扩容。
ConcurrentHashMap不会对整个容器进行扩容,而只是对某个segment进行扩容。
-
size:ConcurrentHashMap判断size时,会统计Segment的count(volatile变量)。但是如果累加后的segment大小发生变化怎么办?为了安全,ConcurrentHashMap会先尝试两次不加锁的统计count,如果发生了变化,则加锁统计。(判断发生变化的方法就是modCount——修改次数是否发生变化)。
-
如果想要获得size,建议使用mappingCount方法,返回long类型。
2. ConcurrentLinkedQueue
基于链接节点的无界线程安全队列,FIFO规则,采用CAS算法,所以是非阻塞队列。
减少更新尾节点的次数,可以减少CAS开销。
入队方法永远返回true,所以不能通过返回值判断元素是否入队成功。
3. 阻塞队列
阻塞队列就是支持两个附加操作的队列,指的是支持阻塞的插入和移除方法。
- 当队列满时,队列会在阻塞插入元素的线程,直到队列不满
- 队列为空时,获取元素的线程会等待队列变为非空
阻塞队列常用于生产者消费者模式
JDK提供了7种阻塞队列
- ArrayBlockingQueue:数组结构组成的有界阻塞队列,默认情况下不保证公平性(阻塞的线程都可以争夺锁——为了提高吞吐量)。
- LinkedBlockingQueue:链表结构组成的有界阻塞队列,默认长度和最大长度均为Integer.MAX_VALUE。
- PriorityBolockingQueue:支持优先级排序的无界阻塞队列,默认情况下采用升序排列,也可以实现compareTo方法来指定顺序。但是不能保证同优先级的顺序。
- DelayQueue:使用优先级队列实现的无界阻塞队列,支持延时获取队列元素,可以指定多久可以获取当前元素。可以作为缓存系统的设计、定时任务调度,实现类TimerQueue,ScheduledThreadPoolExecutor中ScheduledFutureTask。
- SynchronousQueue:不存储元素的阻塞队列,每一个put操作都要等待一个take操作,否则不能继续添加元素。可以创造公平性访问。负责把生产者线程处理的数据直接传给消费者线程,非常适合传递场景。吞吐量也高于ArrayBlockingQueue和LinkedBlockingQueue。
- LinkedTransferQueue:链表结构组成的TransferQueue队列,相比较其他的,LinkedTransferQueue多了tryTransfer和transfer方法。
- LinkedBlockingDeque:由链表组成的双向阻塞队列,多线程同时入队列时,减少了一半的竞争。
4. Fork/Join
Fork/Join是任务分割和任务合并,相当于递归对线程。继承RecursiveTask的任务,交给ForkJoinPool处理。
RecursiveTask抽象类->ForkJoinTask抽象类->Future接口
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
//递归任务
public class CountTask extends RecursiveTask<Integer> {
private static final int THRESHOLD = 2 ;
private int start ;
private int end ;
public CountTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
System.out.println(start + " " + end);
int sum = 0 ;
boolean canCompute = (end - start) <= THRESHOLD ;
if (canCompute){
for (int i=start ; i<=end ; i++){
sum += i ;
}
}else{
int middle = (start + end) / 2 ;
CountTask leftTask = new CountTask(start , middle) ;
CountTask rightTask = new CountTask(middle+1 , end) ;
leftTask.fork() ;
rightTask.fork() ;
int leftResult = leftTask.join() ;
int rightResult = rightTask.join() ;
sum = leftResult + rightResult ;
}
return sum ;
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool() ;
CountTask countTask = new CountTask(1 , 10) ;
Future<Integer> result = forkJoinPool.submit(countTask) ;
try {
int x = result.get() ;
System.out.println(x);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}