单例模式:程序从系统开始到结束只产生唯一的实例,并且提供该单例的全局访问点,尤其在系统程序中出现功能性地冲突时,就需要用到单例模式。
常见的单例有:日历、配置文件、IOC容器等。
单例的写法分为:饿汉式、懒汉式、注册式、序列化
饿汉式:是指该实例没被调用前,程序运行了系统就初始化保持该实例,就显得比较饥饿,迫不及待,该方式如果系统存在的单例比较多,在系统初始化时就比较消耗内存。
/**
* 饿汉式单例
*/
public class HungrySingleton {
private HungrySingleton(){}
private final static HungrySingleton INSTANCE = new HungrySingleton();
public final static HungrySingleton getInstance(){
return INSTANCE;
}
}
懒汉式:是指该实例被初次调用时才去实例化并保存,就显得比较懒,但是这种就避免了饿汉式内存消耗的问题。
/**
* 懒汉式
*/
public class LazySingleton {
private LazySingleton(){}
private static LazySingleton INSTANCE = null;
public final static LazySingleton getInstance(){
if(INSTANCE==null){
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
上面这种单例写法容易发生并发线程安全问题,可以写个发令枪计数器模拟下线程并发的情况,发现并发时候,在INSTANCE为null时,多个线程同时访问时,会出现INSTANCE==null都判断为true。模拟代码:
public class SingletonTest {
public static void main(String[] args) {
/**
* 单例测试
*/
int count = 200;
final CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
new Thread(){
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
LazySingleton singleton = LazySingleton.getInstance();
System.out.println(System.currentTimeMillis()+"->"+singleton);
}
}.start();
latch.countDown();
}
}
}
在懒汉式获取单例的方法加上synchronized关键字无疑能很好的解决线程安全的问题,但是同样会引来了性能损耗的问题(线程并发访问getInstance()就会出现阻塞问题)。
/**
* 懒汉式
*/
public class LazySingleton {
private LazySingleton(){}
private static LazySingleton INSTANCE = null;
public synchronized final static LazySingleton getInstance() {
if(INSTANCE==null){
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
利用静态内部类方式:
/**
* 懒汉式
*/
public class LazySingleton {
private LazySingleton(){}
//static提供全局访问
//final防止被重写重载
public final static LazySingleton getInstance() {
return LazyHolder.INSTANCE;
}
//利用静态内部类方式,默认不加载,LazySingleton被初始化时才加载,
// 这样既保证饿汉式的内存损耗问题,又可以友好地避免线程安全问题
private static class LazyHolder{
private final static LazySingleton INSTANCE = new LazySingleton();
}
}
注册式:是指将单例对象注册到容器中或者利用枚举方式。
/**
* 注册式 -- 容器
*/
public class RegisterSingleton {
private final static Map container = new HashMap();
private RegisterSingleton(){}
public final static RegisterSingleton getInstance(String name){
if(name == null) {
name = RegisterSingleton.class.getName();
}
if(container.get(name) == null){
container.put(name,new RegisterSingleton());
}
return (RegisterSingleton)container.get(name);
}
}
/**
* 注册式--枚举
*/
public enum RegisterEnum {
INSTANCE
}
序列化:可以利用序列化将对象写入文件或者流中,再用反序列化读取文件或者流得到该对象。
/**
* 序列化
*/
public class SerialSingleton implements Serializable {
private static final long serialVersionUID = 5845248228939246644L;
private SerialSingleton(){}
private final static SerialSingleton INSTANCE = new SerialSingleton();
public final static SerialSingleton getInstance() {
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
public static void main(String[] args) {
SerialSingleton s1 = getInstance();
SerialSingleton s2 = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(s1);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
s2 = (SerialSingleton)ois.readObject();
System.out.println(s1 == s2); //true
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
单例反射入侵:由于反射机制,单例的模式都会存在被破坏问题,这无疑会对单例产生入侵问题,我们可以在该单例的构造里判断改单例是否被初始化,被初始化了让它抛出异常即可。
/**
* 饿汉式单例
*/
public class HungrySingleton {
private static boolean isInitialed = false;
private HungrySingleton(){
synchronized (HungrySingleton.class){
if(isInitialed){
isInitialed = !isInitialed;
}else{
throw new RuntimeException("this singleton has been initialed!");
}
}
}
private final static HungrySingleton INSTANCE = new HungrySingleton();
public final static HungrySingleton getInstance(){
System.out.println(System.currentTimeMillis()+"->"+INSTANCE);
return INSTANCE;
}
}