ConcurrentHashMap
线程安全且高效,并发中使用HashMap可能导致程序死循环,而线程安全的HashTable效率又非常地下,
线程不安全的HashMap
多线程下,HashMap进行put操作会引起死循环,导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,最终产生死循环获取Entry,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。
HashTable容器使用synchronized关键字保证线程安全,竞争激烈情况下效率低下,一个线程访问同步方法时,其他线程访问同步方法时会进入阻塞或轮询。
ConcurrentHashMap使用锁分段技术,将数据分段,每段一把锁,不同线程访问不同的数据段,一个线程占用一个数据段的锁时,不影响其他段被其他线程访问。
ConcurrentLinkedQueue
线程安全的队列,安全的实现的方式:1.通过一个锁(入队和出队同一把锁)或两个锁实现,阻塞。2.使用循环CAS的方式来实现,非阻塞。
ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,采用FIFO的规则进行排序,使用volidate和CAS来实现。
阻塞队列
一个支持两个附加操作的队列,支持阻塞的插入和移除方法。当前队列满时,队列会阻塞插入元素的线程,直到队列不满。而移除时,当队列为空时,获取元素的线程会阻塞等待队列变为非空。
- 抛出异常----当队列满时,在插入元素抛出异常,为空时,再获取元素,抛出异常
- 返回特殊值----当往队列插入元素时,会返回元素是否插入成功,成功返回true,移除的话,没有返回null。
- 一直阻塞----队列满了,再插入,会阻塞直到队列不满或响应中断,队列为空时,获取元素会阻塞直到队列不为空。
- 超时退出----当阻塞队列满时,再插入,阻塞一定时间后退出。
无界队列不会出现满的情况,所以put或offer永远不会阻塞。
提供了7个阻塞对列
ArrayBlockingQueue:数组构成的有界阻塞队列,队列按照FIFO原则进行排序。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列,FIFO,默认最大长度Integer.MAX_VALUE。
PriorityBlockingQueue:一个优先级排序的无界阻塞队列,自然序升序,可自定义实现compareTo方法指定元素排序。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousdQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:链表构成的无界阻塞队列。
LinkedBlockingDeque:一个由链表构成的双向阻塞队列。
ArrayBlockingQueue
使用ReentrantLock和 Condition,等待/通知模式。
/** Main lock guarding all access */ final ReentrantLock lock; /** Condition for waiting takes */ private final Condition notEmpty; /** Condition for waiting puts */ private final Condition notFull; public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { //如果满了阻塞 while (count == items.length) notFull.await(); enqueue(e); } 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(); } } public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) return false; else { enqueue(e); return true; } } finally { lock.unlock(); } } public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } } /** * Inserts element at current put position, advances, and signals. * Call only when holding lock. */ private void enqueue(E x) { // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++; notEmpty.signal(); } /** * Extracts element at current take position, advances, and signals. * Call only when holding lock. */ private E dequeue() { // assert lock.getHoldCount() == 1; // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); notFull.signal(); return x; }
LinkedBlockingQueue
使用了两个lock,插入和获取分别一个,以及两个Condition
/** Lock held by take, poll, etc */ private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */ private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, etc */ private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */ private final Condition notFull = putLock.newCondition();
PriorityBlockingQueue
private final ReentrantLock lock; private final Condition notEmpty; public boolean offer(E e) { if (e == null) throw new NullPointerException(); final ReentrantLock lock = this.lock; lock.lock(); int n, cap; Object[] array; while ((n = size) >= (cap = (array = queue).length)) tryGrow(array, cap); try { Comparator<? super E> cmp = comparator; if (cmp == null) siftUpComparable(n, e, array); else siftUpUsingComparator(n, e, array, cmp); size = n + 1; notEmpty.signal(); } finally { lock.unlock(); } return true; } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); E result; try { while ( (result = dequeue()) == null) notEmpty.await(); } finally { lock.unlock(); } return result; }
DelayQueue
队列使用PriorityQueue,队列中的元素必须实现Delayed接口,可指定多久才能从队列中获取当前元素,延迟期满后,才能从队列中提取元素。缓存系统设计、定时任务设计。
private final transient ReentrantLock lock = new ReentrantLock(); private final PriorityQueue<E> q = new PriorityQueue<E>(); leader是一个等待获取队列头部元素的线程。如果leader不等于空, 表示已经有线程在等待获取队列的头元素。 所以,使用await()方法让当前线程等待信号。 如果leader等于空,则把当前线程设置成leader, 并使用awaitNanos()方法让当前线程等待接收信号或等待delay时间 private Thread leader = null; private final Condition available = lock.newCondition(); public boolean offer(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { q.offer(e); if (q.peek() == e) { leader = null; available.signal(); } return true; } finally { lock.unlock(); } } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { E first = q.peek(); if (first == null) available.await(); else { long delay = first.getDelay(NANOSECONDS); if (delay <= 0) return q.poll(); first = null; // don't retain ref while waiting if (leader != null) available.await(); else { Thread thisThread = Thread.currentThread(); leader = thisThread; try { available.awaitNanos(delay); } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && q.peek() != null) available.signal(); lock.unlock(); } }
SynchronousQueueSynchronousQueue 是一个不存 储 元素的阻塞 队 列。每一个 put 操作必 须 等待一个 take 操作,否则阻塞 不能 继续添加元素。![]()
public SynchronousQueue(boolean fair) { transferer = fair ? new TransferQueue<E>() : new TransferStack<E>(); }
SynchronousQueue 可以看成是一个 传 球手, 负责 把生 产 者 线 程 处 理的数据直接 传递给 消 者线 程。 队 列本身并不存 储 任何元素,非常适合 传递 性 场 景。 SynchronousQueue的吞吐量高于LinkedBlockingQueue 和 ArrayBlockingQueue 。LinkedTransferQueue相 对 于其他阻 塞队 列, LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。
- 如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法 时),transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等 待接收元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。
tryTransfer 方法是用来 试 探生 产 者 传 入的元素是否能直接 传给 消 费 者。如果没有消 费 者等待接收元素,则 返回 false 。和 transfer 方法的区 别 是 tryTransfer 方法无 论 消 费 者是否接收,方法 立即返回,而transfer 方法是必 须 等到消 费 者消 费 了才返回。LinkedBlockingDeque链 表 结 构 组 成的双向阻塞 队列,可从两端插入或移除元素。在初始化LinkedBlockingDeque时可以设置容量防止其过度膨胀。另外,双向阻塞队列可以运用在“工作窃取”模式中。阻塞队列的实现原理
使用通知模式,当生产者向满的队列中添加元素时会阻塞生产者,当消费者消费队列中的一个元素时,会通知当前队列可用。使用Condition来实现,阻塞生产者主要通过LookSupport.park(this)。只有4种方式才能从该方法返回。
- 与park对应的unpark执行或已经执行(先执行unpark,再执行park)时,
- 线程被中断
- 等待time后
- 异常现象发生
Fork/Join把一个大任务分割为若干个小任务,最终汇总每个小任务结果得到大任务结果的框架。Fork将大任务切分为若干个子任务并行,Join则是合并这些子任务的执行结果,得到大任务的结果。
工作窃取:某个线程从其他队列里窃取任务来执行。把任务划分为多个互不相干的子任务,每个子任务放到不同的队列中,并为每个队列分配一个线程来执行队列中的任务,线程与队列一一对应,如果一个队列里的任务执行完了,但是另一个队列还为完成,这是完成的线程到未完成的队列中窃取一个任务来执行。这是会两个线程同时访问队列,为了避免竞争,使用双端队列,被窃取的任务从双端队列的头部拿去任务,窃取任务的线程从尾部拿去任务。 充分利用线程进行并行计算,减少线程之间的竞争。但是当队列里只剩下一个任务是,还是存中竞争,而且窃取算法会消耗更多的系统资源,多个线程和多个双端队列。
必须继承 RecursiveTaskstatic class CountTask extends RecursiveTask<Integer> { //是否分割任务的阈值 private static final int YUE = 2; private int start; private int end; public CountTask(int start, int end) { this.end = end; this.start = start; } @Override protected Integer compute() { int sum = 0; boolean canCompute = (end - start) <= YUE; if (canCompute) { for (int i = start; i < end; i++) { sum += i; } } else { int middle = (start + end) / 2; CountTask left = new CountTask(start, middle); CountTask right = new CountTask(middle + 1, end); //执行子任务 left.fork(); right.fork(); //合并结果 sum = left.join() + right.join(); } return sum; } } public static void main(String[] args) throws InterruptedException, ExecutionException { ForkJoinPool pool = new ForkJoinPool(); CountTask task = new CountTask(1, 4); ForkJoinTask<Integer> result = pool.submit(task); result.get(); //执行过程中主线程是无法捕获异常的。 //通过判断来得知 task.isCompletedAbnormally();//是否已经抛出异常或已经取消。 }
原理:
public final ForkJoinTask<V> fork() { Thread t; if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) //异步执行这个任务 ((ForkJoinWorkerThread)t).workQueue.push(this); else ForkJoinPool.common.externalPush(this); return this; } final void push(ForkJoinTask<?> task) { ForkJoinTask<?>[] a; ForkJoinPool p; int b = base, s = top, n; if ((a = array) != null) { // ignore if queue removed int m = a.length - 1; // fenced write for task visibility U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task); U.putOrderedInt(this, QTOP, s + 1); if ((n = s - b) <= 1) { if ((p = pool) != null) //当前任务存放在ForkJoinTask数组队列里。 //然后再调用ForkJoinPool的 //signalWork()方法唤醒或创建一个工作线程来执行任务 p.signalWork(p.workQueues, this); } else if (n >= m) growArray(); } }
public final V join() { int s; if ((s = doJoin() & DONE_MASK) != NORMAL) reportException(s); return getRawResult(); } private int doJoin() { int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w; //通过任务状态来判断返回什么结果。 任务状态有4种:已完成(NORMAL)、被取消(CANCELLED)、信号(SIGNAL)和出现异常 (EXCEPTIONAL) 如果任务状态是已完成,则直接返回任务结果。 如果任务状态是被取消,则直接抛出CancellationException。 如果任务状态是抛出异常,则直接抛出对应的异常。 return (s = status) < 0 ? s : ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? (w = (wt = (ForkJoinWorkerThread)t).workQueue). tryUnpush(this) && (s = doExec()) < 0 ? s : //阻塞当前线程等待获取结果 wt.pool.awaitJoin(w, this, 0L) : externalAwaitDone(); } final int doExec() { int s; boolean completed; if ((s = status) >= 0) { try { completed = exec(); } catch (Throwable rex) { return setExceptionalCompletion(rex); } if (completed) s = setCompletion(NORMAL); } return s; } 首先通过查看任务的状态,看任务是否已经执行完成,如果执行完成, 则直接返回任务状态; 如果没有执行完,则从任务数组里取出任务并执行。 如果任务顺利执行完成,则设置任务状态为NORMAL, 如果出现异常,则记录异常,并将任务状态设置为EXCEPTIONAL