单例模式
为什么要使用单例?
保证一个类在内存中的对象唯一性,减少对象在初始化过程中消耗大量的内存,导致性能降低。
- 饿汉式
public class SingletonDemo1 {
private SingletonDemo1(){}
private static SingletonDemo1 s = new SingletonDemo1();
public static SingletonDemo1 getInstance(){return s;
}
}
- 优点:没有加锁,执行效率会提高,线程安全(每次使用都会实例化对象)。
- 缺点:类加载时就初始化,浪费内存。
2.懒汉式 (线程不安全:创建和使用时,多线程之间互换)
public class SingletonDemo2 {
private SingletonDemo2(){}
private static SingletonDemo2 s = null;
public static SingletonDemo2 getInstance(){
if(s == null){
s=new SingletonDemo2();
}
return s;
}
}
- 优点:第一次调用才初始化,避免内存浪费。
- 缺点:必须加锁synchronized 才能保证单例,但加锁会影响效率。
懒汉式使用双重加锁(JMM的指令重排(多核cpu):加上volatile--但用反射可破坏单例):
public class SingletonDemo2_1 {
private SingletonDemo2_1() {}
private static volatile SingletonDemo2_1 s = null;
public static SingletonDemo2_1 getInstance() {
if (s == null) {
synchronized (SingletonDemo2_1.class) {
if (s == null) {
s = new SingletonDemo2_1();
}
}
}
return s;
}
}
(3)枚举单例(推荐:反射不能破解)
public enum SingletonDemo3 {
INSTNCE;
private InnerClass _instance;
private SingletonDemo3(){
_instance = new InnerClass(); }
public InnerClass getInstance(){
return _instance;}
private static class InnerClass{}
}
(4)登记式模式(反射可破坏单例)
每使用一次,都往一个固定的容器中去注册并且将使用过的对象进行缓存,下次去取对象的时候,就直接从缓存中取值,以保证每次获取的对象都是同一个对象
public class SingletonDemo4 {
private static Map<String, SingletonDemo4> map = new HashMap<String, SingletonDemo4>();
static {
SingletonDemo4 singleton = new SingletonDemo4();
map.put(singleton.getClass().getName(), singleton);
}
private SingletonDemo4() { }
//静态工厂方法 返回此类唯一的实例
public static SingletonDemo4 getInstance(String name) {
if (name == null) {
name = SingletonDemo4.class.getName(); }
if (map.get(name) == null) {
try {
map.put(name, (SingletonDemo4) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return map.get(name);
}
}
5 序列化和反序列划保证单例:重写readResolve;
序列化就是说把内存中的状态通过转换成字节码的形式,从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO),内存中状态给永久保存下来了
反序列化将已经持久化的字节码内容,转换为IO流,通过IO流的读取,进而将读取的内容转换为Java对象,在转换过程中会重新创建对象new
public class SingletonDemo6 implements Serializable{
public final static SingletonDemo6 INSTANCE = new SingletonDemo6();
private SingletonDemo6(){}
public static SingletonDemo6 getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}
测试能否用反射破坏单例(只有枚举单例不能进行反射):
public class TestReflection {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
System.out.println(SingletonDemo6.getInstance().hashCode());
System.out.println("-----");
Class<?> c1 = SingletonDemo6.class;
Constructor<?> constructor = c1.getDeclaredConstructor();
constructor.setAccessible(true);
Object o = constructor.newInstance();
System.out.println(o.hashCode());
}
}
测试所用时间:
public class TestMultiThread {
public static void main(String[] args) {
final Set<Object> instanceSet = Collections.synchronizedSet(new HashSet<>());
int count = 1000;
CountDownLatch latch = new CountDownLatch(count);
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// instanceSet.add(SingletonDemo1.getInstance()); //饿汉式消耗内存,用时:1260
// instanceSet.add(SingletonDemo2.getInstance()); //懒汉式线程不安全,用时:1185
// instanceSet.add(SingletonDemo2_1.getInstance());//懒汉式双重锁反射会破坏,用时:1291
// instanceSet.add(SingletonDemo3.INSTNCE.getInstance());//枚举单例(推荐)用时:2235
// instanceSet.add(SingletonDemo4.getInstance(null));//登记模式(推荐)用时:1367
testSingletonDemo6(); //序列化和反序列划保证单例 用时3618:
latch.countDown();
}
}).start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("===" +(end-start));
System.out.println(instanceSet.size());
}
public static void testSingletonDemo6(){
SingletonDemo6 s1 = null;
SingletonDemo6 s2 = SingletonDemo6.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("Seriable.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("Seriable.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SingletonDemo6)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}