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

本篇博文主要是初步试用并测试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);
}

“`

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值