单例模式
工厂本身、配置文件、日历
1. 饿汉式
类加载的时候就实例化,并且创建单例对象,避免线程安全问题。
public class Hungry {
private Hungry() {
}
private static final Hungry hungry = new Hungry();
public static Hungry getHungry() {
return hungry;
}
}
可以模拟多线程对饿汉式进行测试
多次运行地址均相同,为同一对象,线程安全
public static void main(String[] args) {
int count = 100;
CountDownLatch latch = new CountDownLatch(count);
while (count > 0) {
count--;
new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Hungry.getHungry());
}).start();
latch.countDown();
}
}
优点:运行速度快,线程安全
缺点:当类装载的时候就会创建类实例,浪费内存空间
2. 懒汉式
只有在真正使用单例实例的时候才初始化
public class Lazy {
private Lazy() {
}
private static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
}
模拟多线程进行测试
多次运行可能出现对象地址不同,线程不安全
public static void main(String[] args) {
int count = 10;
final CountDownLatch latch = new CountDownLatch(count);
System.out.println("主程序开始执行");
//第一个子线程执行
while (count > 0) {
count--;
new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalDateTime.now() + ":" + Lazy.getInstance());
}).start();
latch.countDown();
}
}
优点:节省内存,按需加载
缺点:线程不安全
如何使懒汉式线程安全呢(既节省空间,又线程安全)?
使用synchronized关键字(但速度较慢)
public class LazySync { private LazySync() { } private static LazySync lazy; public static synchronized LazySync getInstance() { if (lazy == null) { lazy = new LazySync(); } return lazy; } }
也可以使用静态内部类方式
public class LazyStatic { private LazyStatic() { } public static LazyStatic getInstance() { return lazyHandler.LAZY; } // 使用LazyStatic的时候会先初始化内部类 // 如果没有使用的话,内部类是不加载的 private static class lazyHandler { private static final LazyStatic LAZY = new LazyStatic(); } }
但仍然可以使用反射获取实例
@Test public void testClazz() throws Exception { //正常方法获取实例 System.out.println(LazyStatic.getInstance()); //通过反射方式获取私有化构造器 Class<LazyStatic> clazz = LazyStatic.class; Constructor<LazyStatic> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); LazyStatic lazyStatic = constructor.newInstance(); System.out.println(lazyStatic); }
从运行结果中发现,两个对象地址值并不同
可以通过在私有化构造器中添加synchronized关键字避免
public class LazyStatic { private static boolean initialized = false; private LazyStatic() { synchronized (LazyStatic.class) { if (!initialized) { initialized = true; } else { throw new RuntimeException("避免多次实例化"); } } } // final保证这个方法不被重写和重载 public static final LazyStatic getInstance() { return lazyHandler.LAZY; } private static class lazyHandler { private static final LazyStatic LAZY = new LazyStatic(); } }
避免了饿汉式的内存浪费,也解决了synchronized的性能问题
3. 注册登记式
每使用一次都会往一个固定的容器中去注册并且将使用过的对象进行缓存,下次去取对象的时候,直接从缓存中取值,以保证每次获取的都是同一对象(IOC中的单例模)
public class Register {
private Register() {
}
private static Map<String, Object> register = new HashMap<>();
public static Register getInstance(String name) {
if (name == null) {
name = Register.class.getName();
}
if (register.get(name) == null) {
register.put(name, new Register());
}
return (Register) register.get(name);
}
}
注册式单例适用于创建实例非常多的情况,便于管理,是非线程安全的。
4. 枚举式
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
枚举式单例可避免序列化破坏和反射破坏
关于反序列化破坏
通过定义readResolve()方法指定要返回的对象的生成策略