并发成员变量初步方案

package com.nier;

import org.junit.Test;

import java.io.PushbackInputStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Description
 * 1. 背景: 一个系统的war包,部署在多台服务器上,无可使用的Redis
 *          现存在一个频繁"更新数据库操作",导致数据库一直处于高CPU负荷状态
 *          现希望能将频繁"更新数据库操作"集中在一个时间段,不要长时间一直高负荷,导致其他操作数据库的请求受到影响
 * 2. 方案: 针对每个服务器使用一个公有Map,存储需要更新的数据(对数据进行业务逻辑上的合并处理)
 *          再使用定时job取出Map中数据,统一入库
 * @author YuNuoTianMing
 * @date 2018-07-27 10:55
 **/

public class ConcurrentBusiness {

    /**
     * 如果使用普通的Map,那么会爆出ConcurrentModificationException
     * 因为在遍历Map的时候,其他线程竞争性到线程执行权,而集合遍历时有checkForComodification()方法,执行了插入操作,导致modCount和期望值不一致
     * final void checkForComodification() {
     *  if (modCount != expectedModCount)
     *  throw new ConcurrentModificationException();
     * }
     *
     * java.util.ConcurrentModificationException
     * at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
     * at java.util.HashMap$KeyIterator.next(HashMap.java:828)
     * at com.nier.ConcurrentBusiness.testNormalMap(ConcurrentBusiness.java:71)
     */
    public static Map<Integer,Integer> normalMap = new HashMap<Integer, Integer>(256);

    /**
     * 可见性
     * 定义 : 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
     * (该变量如果想保证可见性 需使用关键字volatile)
     *
     * 相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在线程安全的基础上提供了更好的写并发能力,
     * volatile V value; 为了确保读操作能够看到最新的值,将value设置成volatile,这避免了加锁。
     * final Segment<K,V>[] segments;  final K key;  final int hash;  final HashEntry<K,V> next;  final减少锁竞争对于性能的影响
     * 故使用CopyOnWriteArrayList来确保高并发不会有问题
     */
    public static Map<Integer,Integer> concurrentMap = new ConcurrentHashMap<Integer, Integer>(256);



    @Test
    public void testNormalMap() throws InterruptedException {
        //先向集合中添加50个元素,以便后续遍历
        for (int i = -50; i <= -1; i++) {
            normalMap.put(i,i);
        }
        //后续添加元素(开启线程进行竞态干扰)
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i <= 50; i++) {
                            normalMap.put(i,i);
                            try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
                        }
                    }
                }
        ).start();
        //测试遍历时添加元素是否会抛异常
        for (Integer key : normalMap.keySet()) {
            Thread.sleep(10);
            System.out.println("key=" + key + "  value=" + normalMap.get(key));
        }
    }

    @Test
    public void testConcurrentMap() throws InterruptedException {
        //先向集合中添加50个元素,以便后续遍历
        for (int i = -50; i <= -1; i++) {
            concurrentMap.put(i,i);
        }
        //后续添加元素(开启线程进行竞态干扰)
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i <= 50; i++) {
                            concurrentMap.put(i,i);
                            try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
                        }
                    }
                }
        ).start();
        //测试遍历时添加元素是否会抛异常
        for (Integer key : concurrentMap.keySet()) {
            Thread.sleep(10);
            System.out.println("key=" + key + "  value=" + concurrentMap.get(key));
        }
        System.out.println(concurrentMap.size());
    }


    /**
     * 以上两个简单的测试只是解决了在遍历时线程竞争插入数据问题
     * 但是,在Map中数据更新到数据库后,这些已被更新的数据是没有必要保留的
     * 那么需要考虑他们的删除方式
     */
    public static List<Integer> list = new CopyOnWriteArrayList<Integer>();

    @Test
    public void testConcurrentClearProblem() throws InterruptedException {
        //先向集合中添加50个元素,以便后续遍历
        for (int i = -50; i <= -1; i++) {
            list.add(i);
        }
        //后续添加元素(开启线程进行竞态干扰)
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i <= 100; i++) {
                            list.add(i);
                            //这个Thread停顿时间根据不同配置 需调整值,保证从遍历开始到遍历结束都在竞态插入数据
                            try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
                        }
                    }
                }
        ).start();
        //等待开启线程消耗的时间,应调整值,从而保证遍历开始之前有插入数据
        Thread.sleep(120);
        //遍历开启的时候,集合中有多少"已同步"元素,那么iterate元素个数 =  50 + "已同步插入"元素
        System.out.println("begin iterate list.size()=" + list.size());
        for (Integer key : list) {
            Thread.sleep(2);
            System.out.println(key);
        }

        //这个等待可以看出,一但线程被其他修改list的请求抢到,那么这段时间内添加的元素都会被clear掉
        //所以这种处理方式是不可取的
        Thread.sleep(100);
        list.clear();
        System.out.println("---------------- 等待1s ----------------");
        Thread.sleep(1000);
        for (Integer key : list) {
            System.out.println(key);
        }
        /* 结果类似下面,丢失了从创建完毕iterate开始 到 clear()方法完毕期间内插入的元素
        13
        14
                ---------------- 等待1s ----------------
        74
        75*/
    }

    @Test
    public void testConcurrentRemove() throws InterruptedException {
        //先向集合中添加50个元素,以便后续遍历
        for (int i = -50; i <= -1; i++) {
            list.add(i);
        }
        //后续添加元素(开启线程进行竞态干扰)
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i <= 100; i++) {
                            list.add(i);
                            //开启查看线程名字,可以清楚的发现CopyOnWriteArrayList是(CAP)最终一致性的体现
                            //在遍历开启的时候,集合中有多少"已同步"元素,那么iterate就有多少元素
                            System.out.println(Thread.currentThread().getName() + "-" + i);
                            //这个Thread停顿时间根据不同配置 需调整值,保证从遍历开始到遍历结束都在竞态插入数据
                            try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
                        }
                    }
                }
        ).start();
        //等待开启线程消耗的时间,应调整值,从而保证遍历开始之前有插入数据
        Thread.sleep(120);
        //遍历开启的时候,集合中有多少"已同步"元素,那么iterate元素个数 =  50 + "已同步插入"元素
        int sizeCreateIterateFinish = list.size();
        System.out.println("begin iterate list.sizeCreateIterateFinish=" + sizeCreateIterateFinish);
        for (Integer key : list) {
            Thread.sleep(2);
            System.out.println(key);
            //虽然idea针对这种方法会报错,但是 CopyOnWriteArrayList 仅支持这种遍历移除方式
            list.remove(key);
        }
        /*
         //(CopyOnWriteArrayList反而不支持这种在单线程情况下显得比较合理的遍历方式)
         // CopyOnWriteArrayList 源码的注释
         // Not supported. Always throws UnsupportedOperationException.
         // @throws UnsupportedOperationException always; <tt>remove</tt>
         //         is not supported by this iterator.

        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Thread.sleep(2);
            System.out.println(iterator.next());
            iterator.remove();
        }*/
        Thread.sleep(100);

        System.out.println("----------------  ----------------");
        Thread.sleep(1000);
        int afterAddAllElement =list.size();
        System.out.println("after Thread sleep list.afterAddAllElement=" + afterAddAllElement);
        //-50 -1  0-50 一共151个数字
        System.out.println("创建iterate后插入的元素个数加上创建iterate时list的size(会被remove掉的元素)和应该为151=" + (sizeCreateIterateFinish + afterAddAllElement));
        for (Integer key : list) {
            System.out.println(key);
        }
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值