前言
最近好久没有写博文啦,一方面是自己偷懒啦。。另一方面是自己最近在写一个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));
}
...........
}
这样,就可以得到我们正确的结果啦!