概念:什么是单例模式?
答:单例模式(Singleton Pattern):是指确保一个类在任何情况都绝对只有一个实例,并且提供一个全局访问点。在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例。
单例模式的特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点;
单例模式的写法:
- 1 饿汉-Hungry(可用)
- 2 懒汉-Lazy(不推荐)
- 3 线程安全-synchronized(可用)
- 4 双重检测-DoubleCheckLock(推荐)
- 5 内部类-InnerClass(推荐)
- 6 序列化-Serializable(可用)
- 7 容器单例-IOC(可用)
- 8 枚举-Enum(推荐)
- 9 ThreadLocal
逐一分析讲解单例模式的各种写法:
一,单例模式之饿汉-Hungry(可用)
饿汉单例模式在类加载的时候,就初始化,并且创建对象
优点:
- 1.没有加任何锁,执行效率比较高
- 2.用户体验上来说,比懒汉式更好
- 3.线程绝对安全,线程还没有访问的时候,就已经初始化了,不存在访问线程安全问题
缺点:
- 1.类加载的时候就初始化,不管你用不用,都占用内存资源
代码一:直接new
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
//私有化构造函数
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
代码二:在静态代码块中进行初始化(静态代码块也是在类加载的时候执行):
public class HungryStaticSingleton {
//私有化构造函数
private HungryStaticSingleton() {}
private static final HungryStaticSingleton instance;
//静态代码块
static {
instance = new HungryStaticSingleton();
}
public HungryStaticSingleton getInstance() {
return instance;
}
}
二,单例模式之懒汉-Lazy(不推荐)
有线程调用getInstance方法的时候,开始进行初始化
优点:
1.开始不占用内存,只有调用了才初始化实例
缺点:
1.并发的时候,可能会出现多个实例。线程不安全,因为并发的时候,会出现创建多个实例的情况
代码:
public class LazySimpleSingleton {
private static LazySimpleSingleton instance = null;
//私有化构造函数
private LazySimpleSingleton() {}
public static LazySimpleSingleton getInstance() {
if (instance == null) {
instance = new LazySimpleSingleton();
}
return instance;
}
}
代码:多线程调用
// 用两个线程来模拟并发
public class ExecutorThread implements Runnable {
@Override
public void run() {
//获取实例
LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
//打印当前线程名和实例名
System.out.println(Thread.currentThread().getName()+":"+instance);
}
}
// 创建Test类,创建两个线程并启动
public class LazySimpleSingletonTest {
public static void main(String[] args) {
new Thread(new ExecutorThread()).start();
new Thread(new ExecutorThread()).start();
}
}
测试结果:两个线程输出的不是同一个对象。
三、单例模式之线程安全-synchronized(可用)
有线程调用getInstance方法的时候,开始进行初始化
优点:
1.开始不占用内存,只有调用了才初始化
2.线程安全,因为getInstance加上了锁,假如并发访问的话,拿到锁的线程,才能进入方法进行初始化,其他线程排队等待锁
缺点:
1.性能比较低,并发的时候,会出现其他线程等待的情况
2.每一次并发的时候,都会有锁的竞争情况
代码:
public class LazyThreadSingleton {
private static LazyThreadSingleton lazySingleTon = null;
//私有化构造函数
private LazyThreadSingleton() {}
public static synchronized LazyThreadSingleton getInstance() {
if (lazySingleTon == null) {
lazySingleTon = new LazyThreadSingleton();
}
return lazySingleTon;
}
}
四、单例模式之DCL双重检测-DoubleCheckLock(推荐)
有线程调用getInstance方法的时候,开始进行初始化
优点:
1.开始不占用内存,只有调用了getInstance才初始化
2.第一次并发访问存在锁的竞争情况,后面任何一个一次并发都不会存在锁的竞争
缺点:
1.第一次并发的时候,有锁的竞争情况,存在线程等待
2.代码看起来不够优雅
代码:
public class LazyDoubleCheckSingleton {
//私有化构造函数
private LazyDoubleCheckSingleton() {}
// 加上 volatile 关键字,解决 instance = new LazyDoubleCheckSingleton() 处的指令重排序问题
private volatile static LazyDoubleCheckSingleton instance = null;
public static LazyDoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
}
Java中new一个对象,在底层实现是多条语句,重点是 new(申请内存)、invokespecial(初始化)、astore(建立连接),如果 后两者发生指令重排序,此时对象已不为空,且此时其他线程来判断对象,此时得到的对象是一个半初始化的非空对象。
五、单例模式之内部类-InnerClass(推荐)
JVM加载LazyInnerClassSingleton的时候,不会去加载内部类LazyHolder,只有调用getInstance方法的时候,JVM才会去加载LazyHolder,巧妙的避开了线程安全的问题
优点:
1.开始不会占用内存
2.兼顾了synchronized的性能问题
3.利用Java本身的语法特点,巧妙的避免了线程安全的问题
缺点:写法不够优雅
代码:
public class LazyInnerClassSingleton {
//私有化构造函数
private LazyInnerClassSingleton() {
// 避免反射破坏单例
if(LazyHolder.INSTANCE != null){
throw new RuntimeException("不允许非法访问");
}
}
public static LazyInnerClassSingleton getInstance() {
return LazyHolder.INSTANCE;
}
//内部类
private static class LazyHolder {
private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
}
}
六、单例模式之序列化-Serializable(可用)
两个要求:1.实现Seriablizable,2.增加readResolve方法
序列化:将对象或数据结构转为字节序列的过程。例如:网络IO、磁盘、内存中状态给永久保存下来;
反序列化:将序列化后生成的字节序列转为对象或数据结构的过程。
代码:
//实现Serializable
public class SeriableSingleton implements Serializable {
//私有化构造函数
private SeriableSingleton(){}
private static final SeriableSingleton instance = new SeriableSingleton();
public static SeriableSingleton getInstance(){
return instance;
}
//一定要加上这个方法,否则序列化会破坏单例
public Object readResolve(){
return instance;
}
}
对象进行序列化和反序列化的Test:
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = SeriableSingleton.getInstance();
try {
//序列化
FileOutputStream fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.flush();
oos.close();
//反序列化
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
SeriableSingleton s2 = (SeriableSingleton) ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果: System.out.println(s1==s2); 结果为true
七、单例模式之容器单例-IOC(可用)
容器单例适用于创建非常多的实例,便于管理,就像Spring的IoC容器一样。
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className){
Object instance = null;
if(!ioc.containsKey(className)){
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
}catch (Exception e){
e.printStackTrace();
}
return instance;
}else{
return ioc.get(className);
}
}
}
八、单例模式之枚举-Enum(推荐)
优点:可以防止反射破坏单例
public enum EnumSingleton {
INSTANCE;
EnumSingleton(){
System.out.println("新建对象");
}
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){return INSTANCE;}
}
测试代码:
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton instance1 = EnumSingleton.getInstance();
EnumSingleton instance2 = EnumSingleton.getInstance();
System.out.println(instance1==instance2);
}
}
测试结果:true
代码:反射破坏单例测试
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton instance = EnumSingleton.getInstance();
instance.setData(new Object());
try {
Class clazz = EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
c.setAccessible(true);
System.out.println("c="+c);
Object o = c.newInstance(); // 报错java.lang.IllegalArgumentException: Cannot reflectively create enum objects
System.out.println("o="+o);
}catch (Exception e){
e.printStackTrace();
}
}
}
九,单例模式之ThreadLocal
ThreadLocal可以保证同一个线程中保留一份实例,对不同的线程,都有这个实例的一个副本,这样保证线程安全。
在不考虑反射攻击、序列化与反序列化破坏的情况下,ThreadLocal的单例模式的实现也有可取之处:
代码:
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =
new ThreadLocal<ThreadLocalSingleton>() {
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton() {}
public static ThreadLocalSingleton getInstance() {
return threadLocaLInstance.get();
}
}
测试类:创建线程 ExectorThread
public class ExectorThread implements Runnable{
public void run() {
// LazySimpleSingletion instance = LazySimpleSingletion.getInstance();
// LazyDoubleCheckSingleton instance = LazyDoubleCheckSingleton.getInstance();
ThreadLocalSingleton instance = ThreadLocalSingleton.getInstance();
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(Thread.currentThread().getName() + ":" + instance);
}
}
测试类Test:
public class ThreadLocalSingletonTest {
public static void main(String[] args) {
System.out.println(ThreadLocalSingleton.getInstance()); // ThreadLocalSingleton@4554617c
System.out.println(ThreadLocalSingleton.getInstance()); // ThreadLocalSingleton@4554617c
System.out.println(ThreadLocalSingleton.getInstance()); // ThreadLocalSingleton@4554617c
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.println("------------------------------End");
}
}
测试结果:从输出结果可以看到,同一个线程main中的对象是同一个,不同线程t1与t2中的对象不同。保证了一个线程中一个单例副本。
关于破坏单例的两种方式:
1,序列化:
2,反射: