单例模式指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
特点:
单例类只有一个实例对象;
该单例对象必须由单例类自行创建;
单例类对外提供一个访问该单例的全局访问点;
优点:
1.节省内存资源、
2.保证数据内容的一致性
3.避免对共享资源的多重占用。
单例模式的实现:
1.懒汉式:
类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例
缺点:线程不安全,多线程访问时,可能会造成INSTANCE不是同一个对象
volatile 禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
不加volatile在JIT优化时会出问题。
//懒汉式
public class LazySingleton {
//加final必须初始化
private static volatile LazySingleton INSTANCE;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (null == INSTANCE) {
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
public static void main(String[] args) {
//启用100个线程并打印每一个对象的哈希码(同一个对象的哈希码相同)
for (int i = 0; i < 100; i++) {
new Thread(() ->
System.out.println(EnumSingleton.INSTANCE.hashCode())
).start();
}
}
}
解决方案:加锁,syncsynchronized,但是导致效率降低
第一种:
public static synchronized LazySingleton getInstance() {
if (null == INSTANCE) {
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
第二种:比第一种好一点
public static LazySingleton getInstance() {
//防止多次加锁
if (null == INSTANCE) {
//双重检查
synchronized(LazySingleton.class) {
if (null == INSTANCE) {
INSTANCE = new LazySingleton();
}
}
}
return INSTANCE;
}
2.饿汉式:
类加载进内存后就实例化一个对象,jvm保证线程安全;
简单实用,推荐使用
缺点:不管用到与否,类加载时就完成实例化
//饿汉式
public class HungrySingleton {
private static final HungrySingleton INSTANCE = new HungrySingleton();
//外部不能实例化
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
//启用100个线程并打印每一个对象的哈希码(同一个对象的哈希码相同)
for (int i = 0; i < 100; i++) {
new Thread(() ->
System.out.println(EnumSingleton.INSTANCE.hashCode())
).start();
}
}
}
3.静态内部类:
加载外部类是不会加载内部类,可以实现懒加载,只有调用getInstance()方法时才调用内部类去实例化。
//静态内部类
public class Singleton {
private Singleton() {
}
private static class SingletonHonder{
private final static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHonder.INSTANCE;
}
public static void main(String[] args) {
//启用100个线程并打印每一个对象的哈希码(同一个对象的哈希码相同)
for (int i = 0; i < 100; i++) {
new Thread(() ->
System.out.println(EnumSingleton.INSTANCE.hashCode())
).start();
}
}
}
4.枚举单例:
不仅可以解线程同步,还可以防止反序列化
//枚举单例
public enum EnumSingleton {
INSTANCE;
public static void main(String[] args) {
//启用100个线程并打印每一个对象的哈希码(同一个对象的哈希码相同)
for (int i = 0; i < 100; i++) {
new Thread(() ->
System.out.println(EnumSingleton.INSTANCE.hashCode())
).start();
}
}
}