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);
}
}
}
并发成员变量初步方案
最新推荐文章于 2023-04-14 09:44:31 发布