Singleton

时间:2023-03-09 02:56:31
Singleton
1.//懒汉模式
//天生线程不安全,但是效率高
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getSingleton(){
if(singleton==null){
singleton=new Singleton();
System.out.println("实例化Singleton");
}
return singleton;
} } public class Test {
public static void main(String[] args) {
MyThread myThread1=new MyThread();
MyThread myThread2=new MyThread();
MyThread myThread3=new MyThread();
myThread1.start();
myThread2.start();
myThread3.start();
}
//因为继承Thread每次都会新创建一个任务,所以可以达到效果,而使用Runnable不能够达到效果
public static class MyThread extends Thread{
int count=0;
@Override
public void run() {
while(count<100){
count++;
Singleton.getSingleton();
}
}
}
}

此程序输出的结果为:

实例化Singleton
实例化Singleton

说明此时实例化了两个Singleton,线程不安全!

2.在上面做一点改动

//改良版懒汉模式
//线程安全,但是效率低
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getSingleton(){
if(singleton==null){
singleton=new Singleton();
System.out.println("实例化Singleton");
}
return singleton;
} } public class Test {
public static void main(String[] args) {
MyThread myThread1=new MyThread();
MyThread myThread2=new MyThread();
MyThread myThread3=new MyThread();
myThread1.start();
myThread2.start();
myThread3.start();
}
//因为继承Thread每次都会新创建一个任务,所以可以达到效果,而使用Runnable不能够达到效果
public static class MyThread extends Thread{
int count=0;
@Override
public void run() {
while(count<100){
count++;
Singleton.getSingleton();
}
}
}
}

3.饿汉模式

//饿汉模式
//线程安全,但每次加载类的时候都会实例化,若存在大量的此种单例模式,则会实例化很多无用实例
public class Singleton {
private static Singleton singleton=new Singleton();
private Singleton() {}
public static Singleton getSingleton(){
System.out.println("Singleton");
return singleton;
}
}

4.饿汉模式

//饿汉模式,和第三种一样,只不过换个写法

public class Singleton {
private static Singleton singleton = null;
static {
singleton = new Singleton();
} private Singleton() {
} public static Singleton getSingleton() {
return singleton;
}
}

5.静态内部类

//这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):
第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。
因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。
想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,
因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
public class Singleton {
public static class SingletonHolder{
private static final SingletonHolder singletonHolder=new SingletonHolder();
private SingletonHolder(){};
public static SingletonHolder getSingletonHolder(){
return singletonHolder;
}
}
}

6.枚举类(推荐)

public enum Singleton { singleton; //此处可以是任何方法 public void out(){ System.out.println(singleton.hashCode()); } }

使用枚举类作为单例的好处有三点:1.天生线程安全(因为枚举类是线程安全的)2.反序列化后不会创建多个实例 3.防反射攻击(因为枚举类是abstract),反编译如下

public abstract class Singleton extends Enum
{ private Singleton(String s, int i)
{
super(s, i);
} protected abstract void read(); protected abstract void write(); public static Singleton[] values()
{
Singleton asingleton[];
int i;
Singleton asingleton1[];
System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
return asingleton1;
} public static Singleton valueOf(String s)
{
return (Singleton)Enum.valueOf(singleton/Singleton, s);
} Singleton(String s, int i, Singleton singleton)
{
this(s, i);
} public static final Singleton INSTANCE;
private static final Singleton ENUM$VALUES[]; static
{
INSTANCE = new Singleton("INSTANCE", 0) { protected void read()
{
System.out.println("read");
} protected void write()
{
System.out.println("write");
} };
ENUM$VALUES = (new Singleton[] {
INSTANCE
});
}
}

7.双重检查锁定

public class Singleton {
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;
}
}

总结

有两个问题需要注意:

1.如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。

2.如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

对第一个问题修复的办法是:

  1. private static Class getClass(String classname)
  2. throws ClassNotFoundException {
  3. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  4. if(classLoader == null)
  5. classLoader = Singleton.class.getClassLoader();
  6. return (classLoader.loadClass(classname));
  7. }
  8. }

对第二个问题修复的办法是:

  1. public class Singleton implements java.io.Serializable {
  2. public static Singleton INSTANCE = new Singleton();
  3. protected Singleton() {
  4. }
  5. private Object readResolve() {
  6. return INSTANCE;
  7. }
  8. }

更多详情可以访问http://cantellow.iteye.com/blog/838473;

在此感谢这篇文章给我的启示。