本文主要是介绍concurrentHashMap及CopyOnWriteArrayList、Collections.synchronizedList,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
concurrentHashMap是线程安全的hashmap,在java1.8之前的版本,concurrentHashMap结构为一个segment数组默认初始长度为16,每一个segment都是一个hashmap,Segment继承了ReentranLock来加锁,它的put、remove操作都需要获取segment数组的reentranLock锁,get操作不加锁,因为每个键值对的value是volitale,具有可见性,当它被修改后会及时更新进内存可以被其他线程发现,所以get操作不加锁。它的size()即获取键值对的总数量的计算方法是,会先不加锁获取整个segments的每个hashmap长度计算加和2次,如果两次长度相同则认为它的长度为该值,否则会将每一个segment锁住,然后分别计算它们的长度,进行求和,因此在1.8之前的版本获取size最多计算3次。
而hashmap中size()方法返回的是size字段的值,当put时,size会加一,remove时size会减一。
1.7中的结构如图
在jdk1.8后,不再使用segments这种方式,而是使用了hashmap+synchronized+cas来实现线程安全。
以put方法为例:需要指出的是put操作中使用到了for循环,因为在table初始化和casTabAt的过程中可能有其它线程也在进行操作,需要不断尝试。
(1)首先会通过spread()方法计算key的hash值,与HashMap中不同的是它会将结果再与一个Hash_Base做&操作 hash^(hash>>>16) & Hash_Base;之后会判断数组table是否存在,如果不存在将会去创建,再这一过程中可能会存在多个线程同时去创建数组的情况,此时会使用cas的方式,将SIZECTL修改为-1,修改成功则说明获取了创建数组的锁,进行数组创建。
(2)如果数组存在,则会使用hash&(n-1)计算该key对应的数组下标,n为当前数组的长度,如果数组下标对应的node为null,则会使用casTabAt的方式根据key、value创建新的节点,尝试将新节点放入该下标之中,成功则跳出循环
(3)如果数组下标中n头节点node不为null,且它的hash状态是move则说明当前数组正在扩容,当前线程也会去帮助扩容。
(4)若下标node不为null且数组并未扩容,则尝试进入到synchronized方法块中,进行后续操作。该synchrozed修饰的是数组下标中的头节点node对象,想进入代码块,必须获取该节点的monitor对象使用权。进行代码块后,会根据下标节点存储的是链表还是红黑树进行遍历,如果存在key相同的节点则将value进行覆盖,否则创建新节点进行插入,红黑树插入后还会进行相应旋转。
(5)之后会判断链表的长度是否达到了8个,如果是则转化为红黑树。
(6)最后会调用addCount方法,将concurrentHashMap的长度加一,在这个里面还会判断是否需要扩容。ConcurrentHashMap中有两个元素去记录当前元素的总个数,一个是long 类型baseCount、一个CounterCells数组。首先会尝试使用cas的方式对baseCount进行+1操作;如果失败,当counterCells存在则根据Thread探针和m(counterCells数组长度-1)进行&操作获取数组对应下标元素,使用cas的方式将其中的元素值进行+1;
若前面的条件都不满足或执行失败则会进入到一个循环方法之中去,counterCells如果为null则会创建初始长度为2,之后会使用根据thread探针与counerCells长度-1进行与操作获取下标,如果该下标元素存在,则使用cas对其中数据进行+1,否则创建counterCell对象,初始值为1,以cas的形式将其放入对应下标,如果成功则跳出循环,如果多次失败则说明有其他线程也在执行长度+1,且发生了多次碰撞,此时将counterCells数组扩容长度×2,在执行相同的操作直到成功+1为止;
之后会判断当前数组中元素是否已到达负载因子限制,是的话数组会进行扩容。在扩容时,涉及到了节点重新映射的过程,它支持多线程的协助操作,在这个过程中多线程是分段领取任务,比如说当前数组长度为32,则线程1领取24-31,这8个下标的重hash工作,线程2领取16-23下标中元素的重hash,领取的时候是使用cas的方式尝试修改当前没有被领取最大下标的值,比如一开始是cas(filed,31,23),修改成功之后就说明之后0到23还没人认领。
对于数组每一个下标的链表或红黑树来说,和hashmap类似,也是通过key的hash值与原数组长度n取与看是否等于0,将节点分为low和high两个list,然后将list放入新数组原下标i和i+n的位置。
1.8 size()方法
调用sumCount()方法,它其实是把baseCount的值和countercells数组中元素的值进行了加和,不过baseCount和countercell中存储的是long类型元素,size的返回值是int,当加和返回值大于int最大限制时,直接返回的Integer.MAX_VALUE,在ConcurrentHashMap中推荐使用的获取元素数量的方法是MappingCount()方法,它也是调用了sumCount方法,不过返回值是long类型,不会损失精度。
为什么在1.8即使用了cas也使用了synchronized
通过cas的方式来尝试获取锁,当前线程并没有被阻塞,但如果并发量比较大的情况下,对cpu资源消耗比较大,以put方法为例,cas主要用于发生频率比较小或执行比较快的的情景下,比如像创建数组、创建数组下标头节点、数组扩容、size的变化等,避免了将线程阻塞再唤醒造成的资源消耗,而像数组下标链表及红黑树中元素的put这种比较复杂且耗时操作还是使用的synchronized进行加锁,避免大量线程长时间进行自旋获取锁造成cpu资源浪费。
于1.7中使用reentrantLock对segment整个hashmap进行加锁,1.8 使用cas+syncronized的方式,且synchronized仅对数组下标头节点元素加锁,更灵活且粒度更小,减少了大量线程在获取同一个锁时发生阻塞的概率,同时使用cas也避免了大量线程在不必要的地方陷入过早阻塞,影响执行速度
hashtable中put、get、size所有方法都用了synchronized加锁。
Java并发编程总结4——ConcurrentHashMap在jdk1.8中的改进 - everSeeker - 博客园
http://www.importnew.com/28263.html
java为我们提供了一些线程同步的list如Collections.SynchronizedList和CopyOnWriteArrayList
CopyOnWriteArrayList内部有一个ReenTranLock,读操作不加锁,它的set、add、remove操作是需要加锁的,并且会创建一个新的数组,进行操作后将新数组赋值给原数组引用,因此写操作效果很差
List<String> arrayList = Collections.synchronizedList(new ArrayList<String>());它的读写操作都会被synchronized关键字修饰,读性能很差
java.util.concurrent包下
Callable、Future、Executor
package callable;import stu.people;import java.util.concurrent.*;/*** The xxx class for xxx.** @author sunweiliang* @version 1.0, 2019/3/11* @since untitled 1.0.0*/
public class CallableTest {public static void main(String[] args) {// ExecutorService es1 = Executors.newCachedThreadPool();ExecutorService es = Executors.newCachedThreadPool();ExecutorService es1 = Executors.newFixedThreadPool(1);ExecutorService es2 = Executors.newSingleThreadExecutor();ExecutorService es3 = Executors.newScheduledThreadPool(10);callableThread cThread = new callableThread();Future<people> future = es.submit(cThread);//ScheduledExecutorServicetry{people str = future.get();System.out.println(str);} catch(Exception e) {}}}class callableThread implements Callable<people> {@Overridepublic people call() throws Exception {people p = new people();p.setName("swl");p.setOld(123);return p;}
}
ReenTranLock、Condition
Executors、ThreadPoolExecutor、ScheduledThreadPoolExecutor
ConcurrentHashMap、CopyOnWriteArrayList
这篇关于concurrentHashMap及CopyOnWriteArrayList、Collections.synchronizedList的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!