线程安全相关及ConcurrentHashMap总结

a. Volatile 关键字:对 Volatile 域的写入操作 happens-before 于每个后续对同一 Volatile 的读操作。
    并不保证线程安全。Volatile应用于多核系统的多线程应用中,因为每个cpu都有自己的缓存,并不实时更新到主内存。
    多线程环境下,a线程从主内存中读到的数据可能是更改前的,因为b线程更改的数据还在其他核的缓存区。
    Volatile声明的变量可以防止这一情况的发生,读操作必然是写操作写到主内存后的数据。
b.读写锁 - java.util.concurrent.locks.ReentrantReadWriteLock
    (1)readLock() , 进入条件:(a)没有其他线程的写锁 (b) 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
    (2)writeLock() ,进入条件:(a)没有其他线程的读锁 (b) 没有其他线程的写锁
    简单地理解,读锁是共享锁,写锁是排它锁。
c.提高并发量的线程安全工具-ConcurrentHashMap
    (1)穿插 - HashMap的数据结构
    (a)大体结构:散列表(数组) + 链表(1.8后是链表或红黑树)
    (b)根据key算hash,hashcode是根据地址进行高低位混合运算。根据hashcode的值作为下标插入桶中(就是插入数组中),如果已经有元素,
    组装成为链表,新加入的元素作为表头,元素数量大于8个时,组装成红黑树(1.8+)。
    (c)散列表的大小都是2^n,当75%的位置都有元素时,会触发resize,会把散列表扩容一倍,所有元素重排。
    (2)ConcurrentHashMap的数据结构
    (a)先分为16 个 Segment 对象的数组(默认),每个Segment各自维护自己的散列表。
    运行锁机制的是Segment对象(继承于 ReentrantLock 类)。理想状况下允许16个线程并发。
    (b)put操作。
        <1>先算hash,根据hash找到对应的Segment,再在Segment里面加锁进行put操作。
        <2>因为加锁在Segment里面,理想情况下,可以支持 16 个线程执行并发写操作。
    (c)remove操作。
        remove操作不删除原有元素,而是组建一条新的链表到待删除元素的下一元素,当然新的链表是没有待删除元素的。
        旧的链表是没有头的,故新的get操作不会进入旧链表。
    (d)get操作相关。
        get可以在put和remove时正常地读而没有异常产生(因为remove不删除原有元素)。
        如果get到null值,说明发生resize,加锁再读一次(ConcurrentHashMap不允许null值)。

 

2018.12.08

posted @ 2019-07-12 00:32  枫林晚月  阅读(461)  评论(0编辑  收藏  举报