1,单例模式:这里最好是用spring中bean的scope属性之一Singleton来理解。
① 私有静态属性,用于存取类的唯一实例。
② 公共静态方法,用于提供对该唯一实例的存取访问,如果实例未创建,则创建该实例。
③ 用于限制类再次实例化的方式。通常使用私有构建函数的方式来实现。
1.1,双重检查单例模式[推荐用]
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
1.2,静态内部类单例模式[推荐用]
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
总结为什么不使用:
饿汉式:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
懒汉式:单纯的懒汉式线程不安全,虽然可以使用同步方法来弥补这个问题,但是效率不高。
所以都不推荐使用。
我们这里来说明下他的使用场景和是否绝对不可破环。
1,场景举例:你是否想过我们电脑的任务管理器和文件管理的差距。你可以能打开多个任务管理器吗?答案是不能,这就单例模式思维。在实际项目中,什么需要单例模式?无疑全局多使用,切要求多线程高并发,比如金钱总额,这种多途径可改变的。
2.是否绝对不可破环?答案是能被破环。
方式有两种:1,反射。2,序列化+反序列化。
首先来说说使用的问题举例:
package com.poi.testpoi;
public class Singleton { // 三要素
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
public void print() {
System.out.println("你真的很帅");
}
}
public class Test {
public static void main(String[] args) {
Singleton singleton = Singleton.getSingleton();
singleton.print();
}
}
结果:
我们如何破环?
package com.poi.testpoi;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) {
Singleton singleton = Singleton.getSingleton();
System.out.println(singleton.hashCode());
try {
Class<?> clazz = Class.forName("com.poi.testpoi.Singleton");
Constructor[] constructor = clazz.getDeclaredConstructors();
constructor[0].setAccessible(true);
try {
Singleton singleton2 = (Singleton) constructor[0].newInstance(null);
System.out.println(singleton2.hashCode());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
结果:(显然破环了单例模式的原则)
序列化和反序列化对单例模式的影响
package com.poi.testpoi;
import java.io.*;
public class Test {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(Singleton.getSingleton());
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
System.out.println(newInstance == Singleton.getSingleton());
}
}
结果:(两个对象显然不相同)
那么在实际项目中额外注意序列化对单例模式的破环。
那么如何解决这两个问题对单例模式的破话呢?
这里借用别人的话。
hasReadResolveMethod:如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回true
invokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。
主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。
总结下别人的话,简单来说。如下加入readResolve。
public class Singleton implements Serializable{
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
private Object readResolve() {
return singleton;
}
}
如何保证多线程高并发。
单例模式固然如是。但是通常也和加锁一起来保证线程安全。
关于ThreadLocal和Synchonized的常用对比:
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
一、ThreadLocal使用一般步骤:
1、在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。
2、在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。
3、在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。