Java并发包

1.ConcurrentHashMap

ConcurrentHashMap原理分析:https://my.oschina.net/hosee/blog/639352

不变模式,参考《Java高并发程序设计》

简要理解

Hashtable是一个线程安全的类,它使用synchronized来锁住整张Hash表来实现线程安全,即每次锁住整张表让线程独占。

ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了分段锁技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的Hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

如何确定元素位置

三次hash:

  1. 对于一个key,先进行一次hash操作,得到hash值h1,也即h1 = hash1(key);
  2. 将得到的h1的高几位进行第二次hash,得到hash值h2,也即h2 = hash2(h1高几位),通过h2能够确定该元素的放在哪个Segment;
  3. 将得到的h1进行第三次hash,得到hash值h3,也即h3 = hash3(h1),通过h3能够确定该元素放置在哪个HashEntry。
get操作

get操作大部分情况下不需要加锁。不变(Immutable)和易变(Volatile)ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,其结构如下所示:

static final class HashEntry<K,V> {  
     final K key;  
     final int hash;  
     volatile V value;  
     volatile HashEntry<K,V> next;  
 }

不变模式(immutable)是多线程安全里最简单的一种保障方式。通过volatile和final来确保数据安全。

put操作

put操作需要加锁,但是基于concurrencyLevel划分出多个Segment进行K-V存储,避免了每次put操场都锁住整个数组,理想情况下可以有16个线程同时并发无阻塞的操作集合对象。

size操作

size操作与put和get操作最大的区别在于,size操作需要遍历所有的Segment才能算出整个Map的大小,而put和get都只关心一个Segment。假设我们当前遍历的Segment为SA,那么在遍历SA过程中其他的Segment比如SB可能会被修改,于是这一次运算出来的size值可能并不是Map当前的真正大小。所以一个比较简单的办法就是计算Map大小的时候所有的Segment都Lock住,不能更新(包含put,remove等等)数据,计算完之后再Unlock。这是普通人能够想到的方案,但是牛逼的作者还有一个更好的Idea:先给3次机会,不lock所有的Segment,遍历所有Segment,累加各个Segment的大小得到整个Map的大小,如果某相邻的两次计算获取的所有Segment的更新的次数(每个Segment都有一个modCount变量,这个变量在Segment中的Entry被修改时会加一,通过这个值可以得到每个Segment的更新操作的次数)是一样的,说明计算过程中没有更新操作,则直接返回这个值。如果这三次不加锁的计算过程中Map的更新次数有变化,则之后的计算先对所有的Segment加锁,再遍历所有Segment计算Map大小,最后再解锁所有Segment。

初始化参数
  • initialCapacity表示新创建的这个ConcurrentHashMap的初始容量,也就是上面的结构图中的Entry数量。默认值为static final int DEFAULT_INITIAL_CAPACITY = 16;
  • loadFactor表示负载因子,就是当ConcurrentHashMap中的元素个数大于loadFactor * 最大容量时就需要rehash,扩容。默认值为static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • concurrencyLevel表示并发级别,这个值用来确定Segment的个数,Segment的个数是大于等于concurrencyLevel的第一个2的n次方的数。比如,如果concurrencyLevel为12,13,14,15,16这些数,则Segment的数目为16(2的4次方)。默认值为static final int DEFAULT_CONCURRENCY_LEVEL = 16;。理想情况下ConcurrentHashMap的真正的并发访问量能够达到concurrencyLevel,因为有concurrencyLevel个Segment,假如有concurrencyLevel个线程需要访问Map,并且需要访问的数据都恰好分别落在不同的Segment中,则这些线程能够无竞争地自由访问(因为他们不需要竞争同一把锁),达到同时访问的效果。这也是为什么这个参数起名为“并发级别”的原因。
hash算法

这里用到了Wang/Jenkins hash算法的变种,主要的目的是为了减少哈希冲突,使元素能够均匀的分布在不同的Segment上,从而提高容器的存取效率。假如哈希的质量差到极点,那么所有的元素都在一个Segment中,不仅存取元素缓慢,分段锁也会失去意义。

2.CopyOnWriteArrayList

线程安全、读操作无锁的ArrayList。

可参考:http://ifeve.com/java-copy-on-write/

基本思想:当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

CopyOnWrite并发容器用于读多写少的并发场景。缺点:(1)内存占用问题;(2)只能保证数据的最终一致性,不能保证数据的实时一致性。

3.CopyOnWriteArraySet

基于CopyOnWriteArrayList实现,原理类似,add操作要遍历数组以避免添加重复元素。

4.ArrayBlockingQueue

阻塞队列的基本概念以及简单实现 http://ifeve.com/blocking-queues/

如果队列是空的,消费者会一直等待,当生产者添加元素时候,消费者是如何知道当前队列有元素的呢?如果让你来设计阻塞队列你会如何设计,让生产者和消费者能够高效率的进行通讯呢?让我们先来看看JDK是如何实现的。

使用通知模式实现。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。通过查看JDK源码发现ArrayBlockingQueue使用了可重入锁ReentrantLock和Condition来实现。

阻塞队列深入可参考:http://www.infoq.com/cn/articles/java-blocking-queue/

5.AtomicInteger

基本原理:如果结果符合预期结果,就将结果返回,否则不断进行重试,并没有进行同步,兼顾了安全性和性能java.util.concurrent.atomic包下还有很多类,使用这些类可以保证对这些类的诸如“获取-更新”操作是原子性的,从而避发生竞态条件。(http://blog.youkuaiyun.com/hong0220/article/details/38958121)

可参考:http://www.cnblogs.com/timlearn/p/4127616.html

6.ThreadPoolExecutor、Executors

ThreadPoolExecutor并发包下的线程池服务。Executors提供几种静态方法创建常用线程池,但不推荐使用。

可参考:http://ifeve.com/java-threadpool/

7.FutureTask

用于异步获取执行结果或者取消执行任务的场景,通过传递Callable或Runnable任务给FutureTask,直接调用其run方法或者放入线程池中去执行,之后在外部通过其get方法异步获取执行结果。

8.Semphore

控制某资源被同时访问个数,是对锁的扩展,指定多个线程访问某一资源。对于临界区管理代码,程序会限制同时执行这段代码的线程数。

package com.dianping.gmkt.event.datapools.service.schedule;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * Semaphore实现限流案例
 */
public class Main {
    /**
     * 申请信号量准入数,即同时能申请多少个许可
     */
    static Semaphore semaphore = new Semaphore(10);

    public static void main(String[] args) throws Exception {
        final Executor executor = Executors.newFixedThreadPool(1000);
        for (int i = 0; i < 2000; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    executor();
                }
            });
        }
    }
    public static void executor() {
        try {
            if (semaphore.getQueueLength() > 10) {
                System.out.println("wait...");
                return;
            }
            /**
             * 尝试获得一个许可,若存在可用资源,直接返回
             * 否则进入等待队列,不断尝试获得资源,而tryAcquire不会等待
             */
            semaphore.acquire();
            // 模拟耗时的业务逻辑
            Thread.sleep(1000);
            System.out.println("run...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 将信号量释放,以便其他请求可以获得空闲资源
            semaphore.release();
        }
    }

}

 

9.CountDownLatch

等待多线程完成的CountDownLatch,实现异步转同步操作。

可参考:http://ifeve.com/talk-concurrency-countdownlatch/

10.CyclicBarrier

比CountDownLatch更强大,当await的数量到达指定数量后,才继续往下执行。

11.ReentrantLock、Condition

        ReentrantLock reentrantLock = new ReentrantLock();
        int i = 0;
        // 重入示例,一个线程可以多次次获得同一把锁
        reentrantLock.lock();
        reentrantLock.lock();
        try {
            i++;
        } finally {
            reentrantLock.unlock();
            reentrantLock.unlock();
        }

synchronized 、wait()、notify()的增强版,具体体现在:

(1)重入锁提供了中断处理的能力

程序在等待锁的过程中,可以根据需要取消对锁的请求,对处理死锁有一定帮助。

(2)锁申请等待限时

tryLock()方法限时等待,避免死锁。

(3)公平锁

synchronized产生的锁是非公平的,重入锁允许进行设置,一般公平锁的实现成本较高,性能相对也非常低下,默认情况下采用非公平锁。

重入锁的好搭档:Condition条件,await(使当前线程等待,同时释放当前锁)、signal(唤醒一个在等待中的线程)方法、signalAll(唤醒所有等待中的线程)。

12.ReentrantReadWriteLock

读写锁分离,在读多写少的情景下大幅提升读的性能。类似还有ReadWriteLock。

13.volatile

Java中volatile关键字的最全总结

 

 

 

 

 

 

转载于:https://my.oschina.net/u/2939155/blog/1491680

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值