单例模式的一点研究

单例模式(singleton)是常用的设计模式,它只允许一个类被实例化1次。实现机制,定义或声明一个本类类型的静态域用来保存本类的实例,将构造方法私有化,提供一个公有的静态方法用来返回本类的实例。

单例模式有多重实现方法。

例1,饿汉式

/**
 * 单线程下饿汉单例模式
 */
package pattern.singleton.singleThread.hunger;

public class HungerSingleton {

    private static HungerSingleton instance = new HungerSingleton();

    private HungerSingleton() {
        System.out.println("正在实例化HungerSingleton");
    }

    public static HungerSingleton getInstance() {
        return instance;
    }

    public static void print() {
        System.out.println("aaa");
    }

}

此例中HungerSingleton保存了一个本类实例的引用。并提供一个静态方法返回此实例getInstance()。

主函数:

/**
 * 单例模式实验
 */
package pattern.singleton;

import pattern.singleton.singleThread.hunger.HungerSingleton;

public class SingletonTest {

    public static void main(String[] args) {
        HungerSingleton singleton1;
        System.out.println("声明了一个HungerSingleton");
        HungerSingleton.print();
        for (int i = 0; i < 10; i++) {
            singleton1 = HungerSingleton.getInstance();
            System.out.println("singleton1--" + i + " 是 " + singleton1);
        }
    }
}

运行结果:

------------------------------------Console--------------------------------------

声明了一个HungerSingleton
正在实例化HungerSingleton
aaa
singleton1--0 是 pattern.singleton.singleThread.hunger.HungerSingleton@4f1d0d
singleton1--1 是 pattern.singleton.singleThread.hunger.HungerSingleton@4f1d0d
singleton1--2 是 pattern.singleton.singleThread.hunger.HungerSingleton@4f1d0d
singleton1--3 是 pattern.singleton.singleThread.hunger.HungerSingleton@4f1d0d
singleton1--4 是 pattern.singleton.singleThread.hunger.HungerSingleton@4f1d0d
singleton1--5 是 pattern.singleton.singleThread.hunger.HungerSingleton@4f1d0d
singleton1--6 是 pattern.singleton.singleThread.hunger.HungerSingleton@4f1d0d
singleton1--7 是 pattern.singleton.singleThread.hunger.HungerSingleton@4f1d0d
singleton1--8 是 pattern.singleton.singleThread.hunger.HungerSingleton@4f1d0d
singleton1--9 是 pattern.singleton.singleThread.hunger.HungerSingleton@4f1d0d

--------------------------------------------------------------------------------------

从结果上可以看到HungerSingleton的构造方法只被调用了一次。for循环中得到的所有的HungerSingleton的实例的地址都是相同的,也就是HungerSingleton只有一个实例。

#################################################

例2:懒汉式

/**
 * 单线程下懒汉单例模式
 */
package pattern.singleton.singleThread.lazy;

public class LazySingleton {

    private static LazySingleton instance = null;

    private LazySingleton() {
        System.out.println("正在实例化LazySingleton");
    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

    public static void print() {
        System.out.println("aaa");
    }

}

此例中静态域instance的初始值是null。在调用getInstance()中判断instance是否引用一个实例。否则构造一个实例。这样可以保证类在需要实例化时才构造这个实例。而不是在加载这个类时就实例化这个类。

主函数:

/**
 * 单例模式实验
 */
package pattern.singleton;

import pattern.singleton.singleThread.lazy.LazySingleton;

public class SingletonTest {

    public static void main(String[] args) {
        LazySingleton singleton1;
        System.out.println("声明了一个LazySingleton");
        LazySingleton.print();
        for (int i = 0; i < 10; i++) {
            singleton1 = LazySingleton.getInstance();
            System.out.println("singleton1--" + i + " 是 " + singleton1);
        }
    }
}

运行结果:

------------------------------------Console--------------------------------------

声明了一个LazySingleton
aaa
正在实例化LazySingleton
singleton1--0 是 pattern.singleton.singleThread.lazy.LazySingleton@4f1d0d
singleton1--1 是 pattern.singleton.singleThread.lazy.LazySingleton@4f1d0d
singleton1--2 是 pattern.singleton.singleThread.lazy.LazySingleton@4f1d0d
singleton1--3 是 pattern.singleton.singleThread.lazy.LazySingleton@4f1d0d
singleton1--4 是 pattern.singleton.singleThread.lazy.LazySingleton@4f1d0d
singleton1--5 是 pattern.singleton.singleThread.lazy.LazySingleton@4f1d0d
singleton1--6 是 pattern.singleton.singleThread.lazy.LazySingleton@4f1d0d
singleton1--7 是 pattern.singleton.singleThread.lazy.LazySingleton@4f1d0d
singleton1--8 是 pattern.singleton.singleThread.lazy.LazySingleton@4f1d0d
singleton1--9 是 pattern.singleton.singleThread.lazy.LazySingleton@4f1d0d

--------------------------------------------------------------------------------------

跟例1比较,可以看出在调用静态方法print()时。LazySingleton类并没有构造实例,而是在for循环中第一次构造。并且仍能保证单实例。

#################################################

多线程下的单例问题

改造一下例1:

/**
 * 多线程下饿汉单例
 */
package pattern.singleton.multiThread.hunger;

public class HungerSingletonT {

    private static HungerSingletonT instance = new HungerSingletonT();

    private HungerSingletonT() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("正在实例化HungerSingletonT");
    }

    public static HungerSingletonT getInstance() {
        return instance;
    }

    public static void print(String threadName) {
        System.out.println(threadName + " aaa");
    }

}

在构造方法中增加一个睡眠。

测试类:

package pattern.singleton.multiThread;

import pattern.singleton.multiThread.hunger.HungerSingletonT;

public class MultiThreadTest {

    public static void main(String[] args) {
        SingletonUser s1 = new SingletonUser("s1");
        SingletonUser s2 = new SingletonUser("s2");
    }

}

class SingletonUser implements Runnable {

    private String threadName;

    public SingletonUser(String threadName) {
        this.threadName = threadName;
        Thread t = new Thread(this, threadName);
        t.start();
    }

    @Override
    public void run() {
        HungerSingletonT singleton;
        System.out.println("线程 " + threadName + " 声明了一个singleton");
        HungerSingletonT.print(threadName);
        for (int i = 0; i < 10; i++) {
            singleton = HungerSingletonT.getInstance();
            System.out.println(threadName + " : singleton--" + i + " 是 " + singleton);
        }
    }

}

运行结果:

------------------------------------Console--------------------------------------

线程 s2 声明了一个singleton
线程 s1 声明了一个singleton
正在实例化HungerSingletonT
s2 aaa
s2 : singleton--0 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s2 : singleton--1 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s2 : singleton--2 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s2 : singleton--3 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s2 : singleton--4 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s2 : singleton--5 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s2 : singleton--6 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s2 : singleton--7 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s1 aaa
s1 : singleton--0 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s2 : singleton--8 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s2 : singleton--9 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s1 : singleton--1 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s1 : singleton--2 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s1 : singleton--3 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s1 : singleton--4 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s1 : singleton--5 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s1 : singleton--6 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s1 : singleton--7 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s1 : singleton--8 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7
s1 : singleton--9 是 pattern.singleton.multiThread.hunger.HungerSingletonT@10b30a7

--------------------------------------------------------------------------------------

JVM的运行机制可以保证一个类在被加载的过程中是线程互斥的。饿汉的先行加载使得HungerSingletonT在第一次加载时实例化已完成。所以饿汉式单例是线程安全的。

#################################################

多线程下懒汉式单例问题。

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
设有线程t1和t2,t1在判断完instance==null之后进入到实例化LazySingleton,此时t2也执行到这里,判断instance==null。由于t1的实例化尚未完成,此时instance==null仍为true。所以t2顺利进行new LazySingleton()。于是LazySingleton被实例化了两次。所以懒汉式单例在多线程下是不安全的。

如下例所示:

/**
 * 多线程下的懒汉式单例
 */
package pattern.singleton.multiThread.lazy;

public class LazySingletonT {

    private static LazySingletonT instance = null;

    private LazySingletonT(String str) {
        System.out.println(str + " 正在实例化LazySingletonT");
    }

    public static LazySingletonT getInstance(String str) {
        if (instance == null) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazySingletonT(str);
        }
        return instance;
    }

    public static void print(String str) {
        System.out.println(str + " aaa");
    }

}

测试类:

package pattern.singleton.multiThread;

import pattern.singleton.multiThread.lazy.LazySingletonT;

public class MultiThreadTest {

    public static void main(String[] args) {
        SingletonUser s1 = new SingletonUser("s1");
        SingletonUser s2 = new SingletonUser("s2");
    }

}

class SingletonUser implements Runnable {

    private String threadName;

    public SingletonUser(String threadName) {
        this.threadName = threadName;
        Thread t = new Thread(this, threadName);
        t.start();
    }

    @Override
    public void run() {
        LazySingletonT singleton;
        System.out.println("线程 " + threadName + " 声明了一个singleton");
        LazySingletonT.print(threadName);
        for (int i = 0; i < 10; i++) {
            singleton = LazySingletonT.getInstance(threadName);
            System.out.println(threadName + " : singleton--" + i + " 是 " + singleton);
        }
    }

}

运行结果:

------------------------------------Console--------------------------------------

线程 s2 声明了一个singleton
线程 s1 声明了一个singleton
s2 aaa
s1 aaa
s1 正在实例化LazySingletonT
s1 : singleton--0 是 pattern.singleton.multiThread.lazy.LazySingletonT@10b30a7
s1 : singleton--1 是 pattern.singleton.multiThread.lazy.LazySingletonT@10b30a7
s2 正在实例化LazySingletonT
s2 : singleton--0 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--2 是 pattern.singleton.multiThread.lazy.LazySingletonT@10b30a7
s1 : singleton--3 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--1 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--2 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--3 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--4 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--5 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--6 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--7 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--8 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--9 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--4 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--5 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--6 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--7 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--8 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--9 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb

--------------------------------------------------------------------------------------

不难看出s1和s2分别实例化了一次LazySingletonT。

多线程下如何实现单例模式的延迟加载。我们先来改进一下懒汉式单例,给他增加一个锁synchronized。

例3:

public static synchronized LazySingletonT getInstance(String str) {
        if (instance == null) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazySingletonT(str);
        }
        return instance;
    }

运行结果:

------------------------------------Console--------------------------------------

线程 s2 声明了一个singleton
线程 s1 声明了一个singleton
s2 aaa
s1 aaa
s2 正在实例化LazySingletonT
s2 : singleton--0 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--1 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--2 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--3 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--4 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--5 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--6 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--7 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--8 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s2 : singleton--9 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--0 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--1 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--2 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--3 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--4 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--5 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--6 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--7 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--8 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb
s1 : singleton--9 是 pattern.singleton.multiThread.lazy.LazySingletonT@1a758cb

--------------------------------------------------------------------------------------

看样子问题得到解决了。但新的问题是每一次调用getInstance都必须得到LazySingletonT的锁才行。而事实上只有第一次实例化时才需要锁住LazySingletonT,其他的时候getInstance只是返回已存在的LazySingletonT实例的引用,没必要获得锁。此例虽然解决了单例的互斥问题,但却影响了性能。

#################################################

整个方法需要获得对象锁会影响性能,那么只把实例化的部分放到同步块中会如何呢。

例4:双检锁

/**
 * 双检锁单例模式
 */
package pattern.singleton.multiThread.lazy;

public class DCLSingleton {

    private static DCLSingleton instance = null;

    private DCLSingleton(String str) {
        System.out.println("线程 " + str + " 正在实例化DCLSingleton");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void print(String str) {
        System.out.println(str + " aaa");
    }

    public static DCLSingleton getInstance(String str) {
        if (instance == null) {
            synchronized (DCLSingleton.class) {
                if (instance == null) {
                    instance = new DCLSingleton(str);
                }
            }
        }
        return instance;
    }

}

这里很多帖子这样写synchronized(instance),这会抛出空指针异常(NullPointerException)。因为此时instance是空。线程无法获得对象锁。

测试类:

package pattern.singleton.multiThread;

import pattern.singleton.multiThread.lazy.DCLSingleton;

public class MultiThreadTest {

    public static void main(String[] args) {
        SingletonUser s1 = new SingletonUser("s1");
        SingletonUser s2 = new SingletonUser("s2");
    }

}

class SingletonUser implements Runnable {

    private String threadName;

    public SingletonUser(String threadName) {
        this.threadName = threadName;
        Thread t = new Thread(this, threadName);
        t.start();
    }

    @Override
    public void run() {
        DCLSingleton singleton;
        System.out.println("线程 " + threadName + " 声明了一个singleton");
        DCLSingleton.print(threadName);
        for (int i = 0; i < 10; i++) {
            singleton = DCLSingleton.getInstance(threadName);
            System.out.println(threadName + " : singleton--" + i + " 是 " + singleton);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

运行结果:

------------------------------------Console--------------------------------------

线程 s1 声明了一个singleton
线程 s2 声明了一个singleton
s1 aaa
线程 s1 正在实例化DCLSingleton
s2 aaa
s2 : singleton--0 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s1 : singleton--0 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s2 : singleton--1 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s1 : singleton--1 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s2 : singleton--2 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s1 : singleton--2 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s2 : singleton--3 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s1 : singleton--3 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s2 : singleton--4 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s1 : singleton--4 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s1 : singleton--5 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s2 : singleton--5 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s2 : singleton--6 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s1 : singleton--6 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s2 : singleton--7 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s1 : singleton--7 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s2 : singleton--8 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s1 : singleton--8 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s2 : singleton--9 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7
s1 : singleton--9 是 pattern.singleton.multiThread.lazy.DCLSingleton@10b30a7

--------------------------------------------------------------------------------------

s1,s2两个线程。s1第一次判断instance==null为true,获得对象锁。再次判断instance==null仍为true,开始实例化DCLSingleton。此时s2第一次判断instance==null,由于instance尚未实例化完成,结果仍是true,s2等待对象锁。直到线程s1执行实例化完成释放锁,此时instance不再是空。JVM不保证new语句和赋值语句的执行顺序,但都在同步块中,JVM可以保证这些语句都执行完毕才释放锁。所以,当s2获得对象锁时进行第二次判断instance==null,此时的结果为false。s2不再实例化DCLSingleton。双检锁机制可以实现单例模式。

#################################################

双检锁经过了多次判断,而且仍然有等待的时间。事实上饿汉式已经提供了一个互斥问题的解决方法。就是利用JVM本身的对于类加载过程是互斥的机制。将问题交给虚拟机来优化,提高性能。所要解决的就是延迟加载的问题。就是instance=new Singleton()的new操作只有在需要时才执行。可以把它放在一个类里,这个类在第一次加载时才会执行new操作。内部类可以胜任这个工作。

例5:

package pattern.singleton.multiThread.lazy;

public class ICSingleton {

    private ICSingleton() {
        System.out.println("正在实例化ICSingleton");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static class InnerHandle {
        private static ICSingleton instance = new ICSingleton();
    }

    public static ICSingleton getInstance() {
        return InnerHandle.instance;
    }

    public static void print(String str) {
        System.out.println(str + " aaa");
    }

}

测试类:

package pattern.singleton.multiThread;

import pattern.singleton.multiThread.lazy.ICSingleton;

public class MultiThreadTest {

    public static void main(String[] args) {
        SingletonUser s1 = new SingletonUser("s1");
        SingletonUser s2 = new SingletonUser("s2");
    }

}

class SingletonUser implements Runnable {

    private String threadName;

    public SingletonUser(String threadName) {
        this.threadName = threadName;
        Thread t = new Thread(this, threadName);
        t.start();
    }

    @Override
    public void run() {
        ICSingleton singleton;
        System.out.println("线程 " + threadName + " 声明了一个singleton");
        ICSingleton.print(threadName);
        for (int i = 0; i < 10; i++) {
            singleton = ICSingleton.getInstance();
            System.out.println(threadName + " : singleton--" + i + " 是 " + singleton);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

运行结果:

------------------------------------Console--------------------------------------

线程 s1 声明了一个singleton
线程 s2 声明了一个singleton
s1 aaa
正在实例化ICSingleton
s2 aaa
s1 : singleton--0 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s2 : singleton--0 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s1 : singleton--1 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s2 : singleton--1 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s1 : singleton--2 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s2 : singleton--2 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s2 : singleton--3 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s1 : singleton--3 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s2 : singleton--4 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s1 : singleton--4 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s2 : singleton--5 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s1 : singleton--5 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s1 : singleton--6 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s2 : singleton--6 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s2 : singleton--7 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s1 : singleton--7 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s2 : singleton--8 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s1 : singleton--8 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s2 : singleton--9 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7
s1 : singleton--9 是 pattern.singleton.multiThread.lazy.ICSingleton@10b30a7

--------------------------------------------------------------------------------------

把instance放到一个内部类中。instance不会在ICSingleton加载时初始化。直到调用getInstance方法时,加载内部类InnerHandle才会初始化instance。类在加载时是线程互斥的。这样既保证了ICSingleton的单实例也可以实现延迟加载。

### Java 中单例模式的实现与最佳实践 #### 单例模式概述 单例模式限制类的实例化,并确保 JVM 中只存在该类的一个实例。此类必须提供一个全局访问点来获取唯一的实例[^1]。 #### 应用场景 单例模式广泛应用于日志记录、驱动程序对象管理、缓存机制、线程池配置等领域。此外,在某些设计模式(如抽象工厂、生成器、原型、外观)中也会应用到单例模式。值得注意的是,Java 的标准库中有多个内置组件采用了这种模式,比如 `java.lang.Runtime` 和 `java.awt.Desktop`。 #### 实现方式详解 ##### 1. 饿汉式 (Eager Initialization) 饿汉式的优点在于简单易懂,但在多线程环境下无需额外同步措施即可安全工作。然而,这种方式会在加载类时立即创建实例,即使从未调用过 getInstance 方法也不会改变这一点。 ```java public class Singleton { private static final Singleton instance = new Singleton(); // 私有构造函数防止外部实例化 private Singleton() {} public static Singleton getInstance() { return instance; } } ``` 此方法适用于那些初始化成本不高或者可以接受提前完成的情况[^2]。 ##### 2. 懒汉式 (Lazy Initialization) 懒汉式延迟了实例化的时机直到第一次真正需要它为止。为了保证线程安全性,通常会采用 synchronized 关键字修饰 getInstance 方法。 ```java public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` 虽然这种方法解决了按需加载的问题,但由于每次调用都需要加锁操作,性能上有所牺牲。 ##### 3. 双重检查锁定 (Double-Checked Locking) 通过引入 volatile 关键字并优化判断逻辑,可以在保持线程安全的同时减少不必要的同步开销。 ```java public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); } } } return instance; } } ``` 这是目前较为推荐的一种做法,因为它既实现了懒加载又兼顾到了效率问题。 ##### 4. 静态内部类 (Static Inner Class) 利用静态内部类的特点——只有当首次被引用时才会触发其初始化过程,从而达到懒加载的效果;而且由于JVM本身的特性保障了线程安全性和唯一性。 ```java public class Singleton { private Singleton(){} private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } } ``` 这种方式不仅简洁明了,还具有良好的扩展性和维护性。 ##### 5. 枚举类型 (Enum Type) 枚举类型的天然优势使其成为最简单的单例实现方案之一。因为 Java 编译器会对 enum 进行特殊处理,自动提供了序列化支持和反序列化保护功能。 ```java public enum Singleton { INSTANCE; public void someMethod(){} } ``` 除了上述几种常见形式外,还有基于反射攻击防范的设计思路等高级技巧可供学习研究。 #### 最佳实践建议 - **私有化构造函数**:确保外界无法直接new出新的对象。 - **考虑线程安全**:对于可能并发访问的应用环境要特别注意同步控制策略的选择。 - **避免序列化破坏单例性质**:如果涉及到跨进程通信或持久化存储,则应采取适当手段阻止非法克隆行为的发生。 - **选择合适的加载时刻**:根据实际需求权衡是优先考虑资源占用还是响应速度等因素决定具体实施方案。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值