记录一次并发问题的解决 ThreadLocal

前言

最近好久没有写博文啦,一方面是自己偷懒啦。。另一方面是自己最近在写一个RPC框架,现在该框架已经初步完成了,今天在测试并发的时候,发现总有有报错,经过一番波折,最后解决啦,所以记录下来啦!

过程

在框架的服务发现类 ServiceDiscovery中,我声明了一个用来存储 服务器列表的成员变量 addressList ,并在后面的watchnode方法中为其添加了节点,由于我只有了本地测试,所以addressList 中应该只有一个地址。

    private List<String> addressList = new ArrayList<>();

    public ServiceDiscovery(String registryAddress) {
		
		this.registryAddress = registryAddress;
		this.zk = connectZk();
	}

	
	public synchronized String discovery(String serviceName){
        .....	
	}
	

	//连接 zk客户端
	private ZooKeeper connectZk(){
		.....
	}
	
	
	//监听根节点下的所有子节点
	//遍历所有子节点,获取数据(提供服务的server地址)
	private synchronized void watchNode(final ZooKeeper zk,final String serviceName){  // 
				......
				//遍历所有子节点,把所有数据(地址)  地址列表中
				
				for(String node : zkChildren){
					String path = servicePath + "/" + node;
					byte[] bytes = zk.getData(path, false, null);
			
					addressList.add(new String(bytes));				
				}
		
		        ......
	}

在单线程的情况下程序能正常执行,中间我也打印了addressList的size, 确实是1,和预想的一致。

但是当我起10个线程的时候,程序就有问题了

它提示我的 负载均衡类中报了空指针错误

可是我的代码中只有 addressList 里面的个数 大于1 才会进入负载均衡服务,所以显然在并发环境中,addressList的值受到了干扰,出现了错误。

//如果 提供该服务的只有一个地址,则就返回该地址
		//如果有多个,则随机返回一个
		int size = addressList.size();
		if(size == 1){
			logger.info("=====use the only data: {}=====", addressList.get(0));
			return addressList.get(0);
		}else{
			//使用负载均衡策略
			String data = ServiceLoadBalance.random_LoadBalance(addressList);
			logger.info("=====use the random data: {}=====",data);
			return data;
		}

 后面经过打印输出发现 addressList的size确实不为1,我起多少个线程,它的size就是多少。

这是为什么呢

后来我仔细想想,我的 ServiceDiscovery 是交给spring容器管理的,而spring是单例的,也就是说在多线程的环境下,多个线程操作的实际上是同一个ServiceDiscovery 实例。所以每个serviceDiscovery里的addressList增加时,对其他线程也是有影响的。

 <bean id="serviceDiscovery" class="com.netty.tinyRpc.registry.ServiceDiscovery">
        	<constructor-arg name="registryAddress" value="${registry.address}"></constructor-arg>
        	
        </bean> 	

这显然与我们想要的结果不一致,我们想要每个线程都单独有一个addressList列表。

ThreadLocal

这时候我们的ThreadLocal就派上用场了,讲真这还是我第一次在项目中用使用这个。。。。

ThreadLocal可以使每个线程都保存有一份该变量的副本,相当于每个线程都有一个自己专属的变量,对其他线程是不可见的。简而言之就是各干各的啦。

修改后的代码如下

// server 地址列表 ,提供该服务的可能有多个地址列表
	private static ThreadLocal<List<String>> addressList = new ThreadLocal<>();
//	private List<String> addressList = new ArrayList<>();
	
	private ZooKeeper zk;
	

	public ServiceDiscovery(String registryAddress) {
       ....
	}

	
	public synchronized String discovery(String serviceName){
		....
	}
	

	//连接 zk客户端
	private ZooKeeper connectZk(){
		....
	}
	
	
	//监听根节点下的所有子节点
	//遍历所有子节点,获取数据(提供服务的server地址)
	private synchronized void watchNode(final ZooKeeper zk,final String serviceName){  // 
		        addressList.set(new ArrayList<>());
		        ..........
				
				//遍历所有子节点,把所有数据(地址)  地址列表中
				
				for(String node : zkChildren){
					String path = servicePath + "/" + node;
					byte[] bytes = zk.getData(path, false, null);
					System.out.println(node );
					System.out.println(addressList);
					System.out.println(addressList.get().size());
					addressList.get().add(new String(bytes));				
				}
			
	            ...........
	}

这样,就可以得到我们正确的结果啦!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值