首先单例模式就是说一个类的对象从始至终只有一个,即使再去申请该类的时候,也还是会返回给程序上次已经返回的对象。
单例模式的时候需要注意有两种方法,懒汉式(真正使用的时候才会初始化该对象)和饿汉式(类在加载的时候就会初始化该对象),但是在使用懒汉式的时候就需要注意线同步的问题。
1.懒汉式
普通懒汉式
public Class Singleton {
private static Singleton single = null;
private Singleton(); // 构造方法为私有化,禁止其他类初始化
public static Singleton getInstance() {
if(single == null) {
single = new Singleton();
}
return single;
}
}
这样我们在需要使用该类的对象的时候就直接调用getInstance方法就可以获取到了。
但是有个问题,懒汉式在多线程情况下回出现两个线程先后进入,然后造成对象多次分配的情况。
举个例子,线程一先判断,由于single对象为空,因此开始初始化,但是还没有初始化的时候,线程二又进来判断,这样两个都是判断的为null,结果两个都进行了new,产生了两个对象。
这样就引入了一个新的,带锁的单例模式
加同步的懒汉模式
为了在多线程的情况下能正常工作,对于getInstance方法加上同步
public Class Singleton {
private static Singleton single = null;
private Singleton(); // 构造方法为私有化,禁止其他类初始化
public static synchronized Singleton getInstance() {
if(single == null) {
single = new Singleton();
}
return single;
}
}
这样在线程1进入getInstance方法的时候其他线程就不会进入该方法去进行判断对象是否为空,不会出现多线程的时候不同步的情况。
但同步的懒汉模式有一个性能问题,这样会导致每次进入getInstance都会同步。
双重锁检查
private volatile static Singleton single;
public static Singleton getInstance() {
if(single == null) {
synchronized(Singleton.class) {
if(single == null) {
single = new Singletoon();
}
}
}
return single;
}
只有在single没有被初始化的时候,才会进行同步初始化single对象。
注意这里对于single添加了修饰符volatile,这是为了防止在多线程的情况下出现问题。
2.饿汉模式
饿汉模式就是类在加载的时候就对single进行初始化了。
public class Singleton {
private Singleton single = new Singleton();
private Singleton();
public Singleton getInstance() {
return single;
}
}
对于饿汉式的单例模式而言,天生就是线程安全的,但是在JVM加载类的时候就对单例类的对象进行了初始化,因此如果该对象只用一两次,并且使用时机比较靠后的话,饿汉式单例模式就会影响系统性能
但是懒汉式单例模式,只有在程序中需要使用该对象的时候才会进行初始化,这样就会节省系统资源。
3.实际项目中的一些技巧
前一段时间在项目中碰见了使用jedisPoold的场景,但是在使用JedisPool的时候可以获取到JedisPool的对象,但是在获取Jedis对象的时候就会报不能获取资源的异常。当时自己的代码是这样写的。
public class JedisUtil {
private static JedisPool jedisPool = null;
private static final JedisUtil util = new JedisUtil();
private JedisUtil() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(-1);
config.setMaxIdle(1000);
try {
String redisAddress = 获取redis地址
jedisPool = new JedisPool(config, redisAddress, 6379);
} catch (Exception e) {
e.printStackTrace();
}
}
public static JedisUtil getInstance() {
return util;
}
public static void set(String key, String value) {
Jedis jedis = null;
try {
jedis = getConnect();
jedis.set(key, value);
} catch(JedisConnectionException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
} finally {
if(jedis != null) {
jedis.close();
}
}
}
private static Jedis getConnect() {
return jedisPool.getResource();
}
}
但是由于redisAddress在传过来的时候可能每次不太一样,因此这样写的时候就不是一个单例模式了。
归根到底还是自己对于单例的理解不够,单例说的是一个资源在系统运行过程中只会有一个实例,在这个例子中的资源指的是jedisPool,而不是util,而util在实际使用过程中他可以理解为一个媒介,通过它去使用资源。
最后经过修改,这个工具类可以写为下面的样子
public class JedisUtils {
private static final JedisUtils util = new JedisUtils();
private static Map<String, JedisPool> jedisPools = new ConcurrentHashMap<>();
private JedisUtils() {
}
public static JedisUtils getInstance() {
return util;
}
public static void set(String key, String value, String redisAddress) {
// 真正使用jedispool的时候再从里面获取
// 没有的话先创建jedisPool
if(!jedisPools.containsKey(redisAddress)) {
generateJedisPool(redisAddress);
}
Jedis jedis = null;
try {
jedis = getConnect(redisAddress);
jedis.set(key, value);
} catch(JedisConnectionException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
} finally {
if(jedis != null) {
jedis.close();
}
}
}
private static void generateJedisPool(String redisAddress) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(-1);
config.setMaxIdle(1000);
try {
JedisPool jedisPool = new JedisPool(config, redisAddress, 6379);
jedisPools.put(redisAddress, jedisPool);
} catch (Exception e) {
e.printStackTrace();
}
}
private static Jedis getConnect(String redisAddress) {
JedisPool jedisPool = jedisPools.get(redisAddress);
return jedisPool.getResource();
}
}