笔记:多线程访问ConcurrentHashMap对key加锁

近期由于工作需要,要改写以前的一个小项目,项目是C/S架构,server端部署在tomcat容器中。client端通过api请求访问server端,因此会有并发的要求。

在server端,部署的时候我将初始化几个对象,我将他们保存在map容器中,serverIp作为key,client端并发请求的时候则从这个map中以serverIp为key取出对应的对象来进行下一步操作。这样一来,只要是请求的同一个serverIp,请求就会阻塞并先后执行,不同serverIp则会并发执行。

但是,放在map中的对象可能会由于某些原因失效,或不能用了,这样的话就需要重新初始化这个对象,但是如果第一个请求进来时检测对象需要初始化的时候同时有第二个请求进来的话,两个请求都会去初始化这个对象,并会导致两个请求拿到了不同的对象,会有问题。针对这个情况就需要加锁,有两个办法:

一:可以在获取map里面对应key的对象的方法上加锁,但这样的话就会阻塞掉不同key的并发请求。

二:对map中key对应的对象再次封装,将每个serverIp作为key放入一个单独的map中,再将所有map放入到一个全局唯一的map容器中,并发请求时,针对每一个独立存放key的map加锁。

为此,我写了个demo来验证第二个方案,特此记录下来:

demo类:(用来模拟业务逻辑)

 1 public class daoDemo {
 2     private static Map<String,Map> map = new ConcurrentHashMap<>();
 3 
 4     public daoDemo(){
 5         getMap("one");
 6         getMap("two");
 7     }
 8 
 9     public static void getMap(String key){
10         Map<String,JSch> jSchMap = new ConcurrentHashMap<>();
11         jSchMap.put(key,new JSch());
12         map.put(key,jSchMap);
13     }
14 
15     public static void getInstance(jschBean t1, String key) throws InterruptedException{
16         Map<String,JSch> demo;
17         synchronized (demo = map.get(key)){
18             System.out.println(t1.getName()+" 拿到了demo对象: "+demo.hashCode());
19             Thread.sleep(500);
20             System.out.println(t1.getName()+"更新前:"+map.get(key).hashCode());
21             demo.put(key,new JSch());
22             map.put(key, demo);
23             System.out.println(t1.getName()+"更新后:"+map.get(key).hashCode());
24             System.out.println(t1.getName()+" 释放了demo对象: "+demo.hashCode());
25         }
26 
27     }
28 }

线程实例类:(用来模拟并发请求)

 1 public class jschBean extends Thread{
 2 
 3     String key;
 4     int time;
 5 
 6     public jschBean(String name, String key, int time){
 7         this.setName(name);
 8         this.key = key;
 9         this.time = time;
10     }
11     @Override
12     public void run() {
13         for(int i=0;i<5;i++){
14             try{
15                 daoDemo.getInstance(this, this.key);
16                 Thread.sleep(time);
17             }catch (Exception e){
18                 e.printStackTrace();
19                 System.out.println(this.getName()+" : got exception...");
20             }
21         }
22     }
23 }

程序入口:

 1 public class syncDemo {
 2 
 3     public static void main(String[] args) {
 4         new daoDemo();
 5         jschBean jschBean1 = new jschBean("Thread-1","one",500);
 6         jschBean jschBean2 = new jschBean("Thread-2","one",1500);
 7         jschBean jschBean3 = new jschBean("Thread-3","two",500);
 8         jschBean jschBean4 = new jschBean("Thread-4","two",500);
 9         jschBean1.start();
10         jschBean3.start();
11         jschBean2.start();
12         jschBean4.start();
13     }
14 }

其中,jschBean1和jschBean2为一组,jschBean3和jschBean4为另一组,它们将会模拟两个相同的请求去访问同一个对象。 

 实验结果:

Thread-1 拿到了demo对象: 17092794
Thread-3 拿到了demo对象: 924818233
Thread-1 更新前:17092794
Thread-3 更新前:924818233
Thread-1 更新后:490245531
Thread-3 更新后:1699709137
Thread-1 释放了demo对象: 490245531
Thread-3 释放了demo对象: 1699709137
Thread-2 拿到了demo对象: 490245531
Thread-4 拿到了demo对象: 1699709137
Thread-2 更新前:490245531
Thread-4 更新前:1699709137
Thread-2 更新后:775304961
Thread-2 释放了demo对象: 775304961
Thread-1 拿到了demo对象: 775304961
Thread-4 更新后:2000928397
Thread-4 释放了demo对象: 2000928397
Thread-3 拿到了demo对象: 2000928397
Thread-1 更新前:775304961
Thread-1 更新后:1119255817
Thread-1 释放了demo对象: 1119255817
Thread-3 更新前:2000928397
Thread-3 更新后:672489720
Thread-3 释放了demo对象: 672489720
Thread-4 拿到了demo对象: 672489720
Thread-4 更新前:672489720
Thread-1 拿到了demo对象: 1119255817
Thread-4 更新后:2038713877
Thread-4 释放了demo对象: 2038713877
Thread-3 拿到了demo对象: 2038713877
Thread-1 更新前:1119255817
Thread-1 更新后:1950831806
Thread-1 释放了demo对象: 1950831806
Thread-2 拿到了demo对象: 1950831806
Thread-3 更新前:2038713877
Thread-3 更新后:1994159918
Thread-3 释放了demo对象: 1994159918
Thread-4 拿到了demo对象: 1994159918
Thread-2 更新前:1950831806
Thread-2 更新后:552717495
Thread-2 释放了demo对象: 552717495
Thread-1 拿到了demo对象: 552717495
Thread-4 更新前:1994159918
Thread-4 更新后:2109462013
Thread-4 释放了demo对象: 2109462013
Thread-3 拿到了demo对象: 2109462013
Thread-1 更新前:552717495
Thread-1 更新后:1685769729
Thread-1 释放了demo对象: 1685769729
Thread-3 更新前:2109462013
Thread-3 更新后:78130730
Thread-3 释放了demo对象: 78130730
Thread-4 拿到了demo对象: 78130730
Thread-1 拿到了demo对象: 1685769729
Thread-4 更新前:78130730
Thread-4 更新后:951665710
Thread-4 释放了demo对象: 951665710
Thread-3 拿到了demo对象: 951665710
Thread-1 更新前:1685769729
Thread-1 更新后:814231480
Thread-1 释放了demo对象: 814231480
Thread-2 拿到了demo对象: 814231480
Thread-3 更新前:951665710
Thread-3 更新后:325443208
Thread-3 释放了demo对象: 325443208
Thread-4 拿到了demo对象: 325443208
Thread-2 更新前:814231480
Thread-2 更新后:617503857
Thread-2 释放了demo对象: 617503857
Thread-4 更新前:325443208
Thread-4 更新后:1551467404
Thread-4 释放了demo对象: 1551467404
Thread-2 拿到了demo对象: 617503857
Thread-2 更新前:617503857
Thread-2 更新后:1640810124
Thread-2 释放了demo对象: 1640810124
Thread-2 拿到了demo对象: 1640810124
Thread-2 更新前:1640810124
Thread-2 更新后:1940031347
Thread-2 释放了demo对象: 1940031347
View Code

从结果来看,线程1和线程2是相互阻塞的,并且他们拿到的总是map中保存的key对应对象的最新值。

 

posted @ 2020-03-14 21:27  三季苗  阅读(998)  评论(0编辑  收藏  举报