I discovered a situation during code debugging, that is, when I checked the connections of memcached, I found that it was always maintained at about 100. Of course, this seems to be no problem, because memcached has 1024 connections by default. But what I'm thinking about is why there are 100 or so. Because my memcachedclient is generated in singleton mode, I defined a memcachedClientFactory class. The main code is as follows:
private MemcachedClientFactory(){
}
private MemcachedClientFactory(MemcachedConnectionBuilder memcachedConnectionBuilder, String servers){
this.memcachedConnectionBuilder= memcachedConnectionBuilder;
this.servers=servers;
}
public static MemcachedClient createClient(){
if(memcachedClient==null){
this.memcahcedClien= new MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));
}
return this.memcahcedClient;
}
}
}
Back to the original question, why are there more than 100 connections?
Can the above writing method really guarantee that only one connection will be generated? Obviously not, why? Multi-thread concurrency! The problem lies here. When multiple threads enter the createClient() method at the same time, and they all decide that memcachedClient is null, multiple connections are generated. Ha, the problem was found.
improve:
This is ok, the change is very simple. There is no problem with the program, and it is guaranteed that there is only one connection.
But putting this problem aside, we can continue to think deeply about how to solve the concurrency problem in the singleton mode.
Let me summarize, there are roughly three ways to solve the problem of concurrency in singleton mode:
1. Do not use delayed instantiation, but use early instantiation.
That is, the program is rewritten as:
public static Singleton getInstance(){
return instance;
}
}
When doing this, the jvm creates the instance immediately when loading the class, so the premise of this is that the burden of creating the instance is not large, I don't think too much about performance, and we confirm that the instance will definitely be used . In fact, my previous code can also be used this way:
private MemcachedClientFactory(){
}
private MemcachedClientFactory(MemcachedConnectionBuilder memcachedConnectionBuilder, String servers){
this.memcachedConnectionBuilder= memcachedConnectionBuilder;
this.servers=servers;
}
public static MemcachedClient createClient(){
return this.memcahcedClient;
}
}
}
However, it seems that there is no problem, but there is a hidden danger, that is, once someone accidentally calls the memcachedClient.shutdown() method, the entire program will not be able to generate a new memcachedClient. Of course this is an extreme case, but for the sake of code robustness, it can be changed to:
2. Just use the synchronized keyword.
Doing this can ensure synchronization problems, but we know that the overhead of using synchronized is very high and will seriously affect performance, so the premise of using this is that you confirm that you will not call this method frequently, or that the overhead of creating this instance will not be special. big. Whether it can be improved, see below.
3. Use "double check locking" and use synchronization in getInstance
private Singleton(){};
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}