【Java】并发容器ConcurrentHashMap和CopyOnWriteArrayList(一)

    xiaoxiao2021-04-15  45

    本篇博文主要是初步试用并测试ConcurrentHashMap等并发容器的并发

    主要分为两部分测试

    第一部分测试ConcurrentHashMap和HashMap区别 第二部分测试ConcurrentHashMap的并发能力(利用多线程,定时器测试)

    测试代码1:

    package test; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ConcurrentHashMapTest { public static void main(String[] args) { //测试ConcurrentHashMap能不能put进去value为null的键值对 // ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>(); // map.put("key1",null);//不能put进去null // System.out.println(map); //测试HashMap能不能put进去value为null的键值对 // map1.put(null, null); //测试HashMap的迭代器 Map<String, String> myMap = new HashMap<String, String>(); myMap.put("1", "1"); myMap.put("2", "1"); myMap.put("3", "1"); Iterator<String> it=myMap.keySet().iterator(); while(it.hasNext()){ String key=it.next(); if(key.equals("2")){ myMap.put(key+"new", "newvalue"); } } System.out.println(myMap); //测试ConcurrentHashMap的迭代器 ConcurrentHashMap<String, String> conmap = new ConcurrentHashMap<String, String>(); conmap.put("key1", "value1"); conmap.put("key2", "value2"); conmap.put("key3", "value3"); conmap.put("key4", "value4"); conmap.put("key5", "value5"); Iterator<String> it1=conmap.keySet().iterator(); while(it1.hasNext()){ String key=it1.next(); if(key.equals("key2")){ conmap.put("key6", "value6"); } } System.out.println(conmap); } }

    从上面的测试代码运行结果来看,可以得出以下几个结论:

    (1)HashMap可以put进去key或value为null的键值对(可以同时为null) (2)ConcurrentHashMap是不可以put进去key或者value为null的键值对 (3)对于HashMap的key迭代器,在迭代同时进行数据修改的话,会出现异常java.util.ConcurrentModificationException (4)对于ConcurrentHashMap在迭代器内进行数据修改,是不会出现上面的错误

    其中对于上面的(2)可以通过ConcurrentHashMap的源码可以看出来,ConcurrentHashMap不允许put进去的键值对的键或者值为null,否则就会报错

    /** * Maps the specified key to the specified value in this table. * Neither the key nor the value can be null. * *

    The value can be retrieved by calling the {@code get} method * with a key that is equal to the original key. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with {@code key}, or * {@code null} if there was no mapping for {@code key} * @throws NullPointerException if the specified key or value is null */ public V put(K key, V value) { return putVal(key, value, false); }

    测试代码2:

    package ConcurrentTest; import java.util.Iterator; import java.util.Random; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; public class ConcurrentDataModify { // static boolean flag=false; // static Timer timer; public static ConcurrentHashMap<String, CopyOnWriteArrayList<String>> map = new ConcurrentHashMap<String, CopyOnWriteArrayList<String>>(); public static void main(String[] args) { int i=5; while(i-->0){ System.out.println("第"+i+"次运行!"); CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.add("k"+String.valueOf(i)+"_"+String.valueOf(i+1)); list.add("k"+String.valueOf(i)+"_"+String.valueOf(i+2)); list.add("k"+String.valueOf(i)+"_"+String.valueOf(i+3)); map.put("k"+String.valueOf(i), list); } System.out.println(map); ConcurrentDataModify m=new ConcurrentDataModify(); Timer timer1=new Timer(); TimerTask1 task1=m.new TimerTask1(); task1.timer=timer1; timer1.schedule(task1, 0,1000); // if(flag){ // timer1.cancel(); // } Timer timer2=new Timer(); timer2.schedule(m.new TimerTask1(), 0,200); } class TimerTask1 extends TimerTask{ Timer timer; @Override public void run() { int random1 = new Random().nextInt(5); int random2 = new Random().nextInt(5); int random3 = new Random().nextInt(5); if(map.size()==0){ map.put("k"+String.valueOf(random3), new CopyOnWriteArrayList<String>()); }else{ map.remove("k"+String.valueOf(random1)); CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list=map.get("k"+String.valueOf(random2)); // if(list==null){ // System.out.println("list为空!"); // timer.cancel(); this.cancel(); // return; // } if(list!=null){ if(list.size()==0){ list.add("k"+String.valueOf(random3)+"_"+String.valueOf(random3+1)); }else{ System.out.println(list.size()); Iterator<String> it=list.iterator(); int i=0; while(it.hasNext()){ String cur=it.next(); cur+="_modified"; list.set(i, cur); i++; } } //ConcurrentHashMap的put和replace方法区别? map.put("k"+String.valueOf(random2), list); System.out.println("-------------"); System.out.println(map); // map.replace("k"+String.valueOf(random2), list); } } } } }

    上面的代码模拟了并发修改ConcurrentHashMap和CopyOnWriteArrayList的数据,可以看出来,多线程并发修改数据的情况下,ConcurrentHashMap和CopyOnWriteArrayList是可以支持并发修改的。 可能同时修改某一数据情况也是可以支持的,至于其内部实现,分享一篇很详细的博文:

    Java并发编程:并发容器之ConcurrentHashMap(转载)

    通过上面的测试,得到以下几个结论

    (1)如何立刻取消当前Timer和TimerTask,并不执行后面的代码(当满足某个条件的时候)? 需要在run()方法内部满足该条件时,退出run方法,同时为了使定时任务之后进来的任务也不执行,则需要调用timer的cancel()方法,实现之后任务的终止。 这里需要注意的是Timer和TimerTask这两个类中的cancel()方法的区别。

    下面贴出源码:

    Timer的cancel()方法

    /** * Terminates this timer, discarding any currently scheduled tasks. * Does not interfere with a currently executing task (if it exists). * Once a timer has been terminated, its execution thread terminates * gracefully, and no more tasks may be scheduled on it. * * <p>Note that calling this method from within the run method of a * timer task that was invoked by this timer absolutely guarantees that * the ongoing task execution is the last task execution that will ever * be performed by this timer. * * <p>This method may be called repeatedly; the second and subsequent * calls have no effect. */ public void cancel() { synchronized(queue) { thread.newTasksMayBeScheduled = false; queue.clear(); queue.notify(); // In case queue was already empty. } }

    从上面的注释可以看出来,该方法会舍弃正在排队等候的任务,但是并不会终止正在执行的任务。这也可以通过笔者上面的测试代码进行测试来验证。 这段代码,注释掉return,只保留timer.cancel()之后,会发现,程序仍然在执行,这句代码之后的代码。执行完这一遍之后就不会继续执行之后的任务(因为之后的任务已经被终止了)。

    TimerTask的cancel()方法源码:

    /** * Cancels this timer task. If the task has been scheduled for one-time * execution and has not yet run, or has not yet been scheduled, it will * never run. If the task has been scheduled for repeated execution, it * will never run again. (If the task is running when this call occurs, * the task will run to completion, but will never run again.) * * <p>Note that calling this method from within the <tt>run</tt> method of * a repeating timer task absolutely guarantees that the timer task will * not run again. * * <p>This method may be called repeatedly; the second and subsequent * calls have no effect. * * @return true if this task is scheduled for one-time execution and has * not yet run, or this task is scheduled for repeated execution. * Returns false if the task was scheduled for one-time execution * and has already run, or if the task was never scheduled, or if * the task was already cancelled. (Loosely speaking, this method * returns <tt>true</tt> if it prevents one or more scheduled * executions from taking place.) */ public boolean cancel() { synchronized(lock) { boolean result = (state == SCHEDULED); state = CANCELLED; return result; } } 从上面的代码来看,TimerTask类的cancel()方法并不会取消正在执行的任务,它会等他执行完毕。 因此如果要立刻停止任务,不执行后面的代码,而且之后也不会执行任务,就必须在run()方法内部调用Timer的cancel()方法,同时return。 (2)如果要用flag标记来完成上面说的终止任务的目标(按照我上面的测试代码(已经注释掉)来做的话),是不可以的。至于为什么,读者可以自己测试思考一下。 (3)对于ConcurrentHashMap的put和replace方法区别源码 replace()方法,会返回之前key对应的值 /** * {@inheritDoc} * * @return the previous value associated with the specified key, * or {@code null} if there was no mapping for the key * @throws NullPointerException if the specified key or value is null */ public V replace(K key, V value) { if (key == null || value == null) throw new NullPointerException(); return replaceNode(key, value, null); } put方法,要求key和value都不能为null,也会返回之前key对应的value /** * Maps the specified key to the specified value in this table. * Neither the key nor the value can be null. * * <p>The value can be retrieved by calling the {@code get} method * with a key that is equal to the original key. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with {@code key}, or * {@code null} if there was no mapping for {@code key} * @throws NullPointerException if the specified key or value is null */ public V put(K key, V value) { return putVal(key, value, false); }

    “`

    转载请注明原文地址: https://ju.6miu.com/read-670996.html

    最新回复(0)