2011年12月24日土曜日

[设计模式] 单例模式 (Singleton)

单例是一种创建性模型, 它用来确保只产生一个实例, 并提供一个访问它的全局访问点
单例模式确保某一个类只有一个实例, 而且自行实例化并向整个系统提供这个实例单例模式

1. 场景
1-1. 不分状态或只读状态的共享对象
当拥有一个没有状态之分并且有多个依赖对象的 bean
因为如果没有状态之分就不需要同步, 当每次某个依赖对象在工作中需要使用这个 bean 的时候就不需要创建一个新的实例

1-2. 使用共享状态的共享对象
如果 bean 的状态必须共享, 那么单例是最理想的选择
这种情况下要保证状态回写的同步操作的粒度越小越好

1-4. 具有可记录状态的高吞吐量对象
某个 bean 在程序中被大量使用, 共享对象只有少量的可回写状态, 而这时实例化新的实例将会耗费巨大资源, 那么也许会发现维护一个单例和其所有对象状态回写的同步, 与持续创建上百个该 bean 的实例相比能够提供更好的性能. 使用这种方法一定要努力保存同步的细粒度而不要牺牲一致性.

以下场景需要考虑使用非单例创建模式
1-5. 具有可回写状态的对象
如果 bean 拥有一些可回写状态, 这时会发现对它们进行同步操作要比为每个请求创建一个新的实例耗费的资源更多

1-6. 具有私有状态的对象
某个独立对象需要拥有它私有的状态, 这样它们能够与其所依赖的 bean 单独处理自己的操作, 这种情况下单例显然不合适, 需要使用非单例模式

2. 两种单例模式
懒汉方式: 全局的单例实例在第一次被使用时构建, 延迟初始化
饿汉方式: 全局的单例实例在类装载时构建, 急切初始化

3. 单例模式的缺陷及注意点
3-1. 多线程情况下, 懒加载模式可能导致线程不安全因素 (4-2-2, 4-2-3)
3-2. 当单例类被多个 classloader 加载的情况下, 可能获得多个单例类的实例 (TODO)
3-3. 当单例类实现了序列化接口 Serializer 时, 我们如果将对象序列化, 并反序列化得到实例时, 这个实例将是一个新的实例, 而不是序列化之前的实例 (5)

4. 实现
4-1. 饿汉模式
4-1-1. 适用于单线程与多线程
public class Singleton {
    private Singleton() {}
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }
}

4-2 懒汉
4-2-1. 懒汉经典, 只适用于单线程
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

4-2-2. 懒汉改进 (1) 双重锁, 适用于多线程, volatile, JDK1.5 以上
public class Singleton {
    private volatile static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

4-2-3. 懒汉改进 (2), 适用于多线程, 任何 JDK 版本
public class Singleton {
    private Singleton() {}
    private static class SingletonInstance {
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.instance;
    }
}

4-3. 懒汉模式多线程测试
4-3-1. 待测类
4-3-1-1. 懒汉经典, 单线程成功, 多线程失败
public class Singleton {
    private static Singleton instance = null;
    private static boolean firstThread = true;
    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            simulateRandomActivity();
            instance = new Singleton();
        }
        System.out.println("created singleton: " + instance);
        return instance;
    }

    private static void simulateRandomActivity() {
        try {
            if (firstThread) {
                firstThread = false;
                System.out.println("sleeping...");

                // This nap should give the second thread enough time
                // to get by the first thread.
                Thread.currentThread().sleep(50);
            }
        } catch (InterruptedException ex) {
            System.out.println("Sleep interrupted");
        }
    }
}

4-3-1-2. volatile 改进, 单多线程均可
public class Singleton {
    private volatile static Singleton instance = null;
    private static boolean firstThread = true;
    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    simulateRandomActivity();
                    instance = new Singleton();
                }
            }
        }

        System.out.println("created singleton: " + instance);
        return instance;
    }

    private static void simulateRandomActivity() {
        try {
            if (firstThread) {
                firstThread = false;
                System.out.println("sleeping...");
                Thread.currentThread().sleep(50);
            }
        } catch (InterruptedException ex) {
            System.out.println("Sleep interrupted");
        }
    }
}

4-3-1-3. 静态内部类, 单多线程均可 (推荐)
public class Singleton {
    private static boolean firstThread = true;
    private Singleton() {}
    private static class SingletonInstance {
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        simulateRandomActivity();
        Singleton instance = SingletonInstance.instance;
        System.out.println("created singleton: " + instance);
        return instance;
    }

    private static void simulateRandomActivity() {
        try {
            if (firstThread) {
                firstThread = false;
                System.out.println("sleeping...");
                Thread.currentThread().sleep(50);
            }
        } catch (InterruptedException ex) {
            System.out.println("Sleep interrupted");
        }
    }
}

4-3-2. 多线程测试类
import junit.framework.Assert;
import org.junit.Test;
public class SingletonTest {
    private static Singleton singleton = null;
    public void setUp() { singleton = null; }

    @Test
    public void testUnique() throws InterruptedException {
        // Both threads call Singleton.getInstance().
        Thread threadOne = new Thread(new SingletonTestRunnable());
        Thread threadTwo = new Thread(new SingletonTestRunnable());
        threadOne.start();
        threadTwo.start();
        threadOne.join();
        threadTwo.join();
    }

    private static class SingletonTestRunnable implements Runnable {
        public void run() {
            // Get a reference to the singleton.
            Singleton s = Singleton.getInstance();

            // Protect singleton member variable from
            // multithreaded access.
            synchronized (SingletonTest.class) {
                if (singleton == null)// If local reference is null...
                    singleton = s;// ...set it to the singleton
            }

            // Local reference must be equal to the one and
            // only instance of Singleton; otherwise, we have two
            // Singleton instances.
            Assert.assertEquals(true, s == singleton);
        }
    }
}

5. 单例类的串行化
5-1. 单例类
import java.io.Serializable;
public class Singleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static Singleton instance = new Singleton();
    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }

    // 需要添加此方法
    private Object readResolve() {
        return instance;
    }
}

5-2. 测试类
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import junit.framework.Assert;
import org.junit.Test;
public class SingletonTest {
    @Test
    public void testSerialize() {
        System.out.println("testing singleton serialization...");
        writeSingleton();
        Singleton s1 = readSingleton();
        Singleton s2 = readSingleton();
        Assert.assertEquals(true, s1 == s2);
    }

    private void writeSingleton() {
        try {
            FileOutputStream fos = new FileOutputStream("serializedSingleton");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(Singleton.getInstance());
            oos.flush();
        } catch (NotSerializableException se) {
            System.err
                    .println("Not Serializable Exception: " + se.getMessage());
        } catch (IOException iox) {
            System.err.println("IO Exception: " + iox.getMessage());
        }
    }

    private Singleton readSingleton() {
        Singleton s = null;

        try {
            FileInputStream fis = new FileInputStream("serializedSingleton");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s = (Singleton) ois.readObject();
        } catch (ClassNotFoundException cnf) {
            System.err
                    .println("Class Not Found Exception: " + cnf.getMessage());
        } catch (NotSerializableException se) {
            System.err
                    .println("Not Serializable Exception: " + se.getMessage());
        } catch (IOException iox) {
            System.err.println("IO Exception: " + iox.getMessage());
        }
        return s;
    }
}

6. singleton registry class (TODO)
单例注册表类, 运行期间动态得到单例
解决懒汉饿汉类不能被继承的缺点

0 件のコメント:

コメントを投稿