题目:设计一个类,我们只能生成该类的一个实例。

    如果只能生成一个实例,这意味着构造方法必须为私有的,该类实例的创建不能由用户创建,一个容易想到的版本如下

    1. public class Test01 {
    2. private Test01() {
    3. }
    4. private static Test01 instance;
    5. public static Test01 getInstance() {
    6. if (instance == null) {
    7. instance = new Test01();
    8. }
    9. return instance;
    10. }
    11. public static void main(String[] args) {
    12. Test01 one = Test01.getInstance();
    13. Test01 two = Test01.getInstance();
    14. System.out.println(one == two); // true
    15. }
    16. }

    我们在类里面声明了一个instance变量,它是该类的一个实例,用户通过getInstance()来获得该类的实例,在该方法中,首先判断instance是不是为null,如果是null说明还没有创建过该实例,那么创建一个实例,如果不为null,说明已经创建过该实例,将之前创建过的实例返回,从而达到创建的始终是一个实例的效果。

    但是使用上面的方法有一个缺点,那就是在多线程的情况下可能创建出多个实例,考虑这么一种情况,第一个线程执行if (instance == null)时,这时是成功的,会进入到if语句中,但是这个时候它失去了执行权,这个时候第二个线程执行if (instance == null)时,由于instance还没有赋值,它的值还为null,它也能够进入到if语句中,这个时候第一个线程和第二个线程都会使用new关键字创建出一个实例,它们是不同的。

    简单的解决上面的问题就是加锁,上面会出现问题的语句为

    1. if (instance == null) {
    2. instance = new Test01();
    3. }

    所以我们为这个语句块加上锁就行,这就出现了第二个版本

    1. public class Test02 {
    2. private static Test02 instance;
    3. private Test02() {
    4. }
    5. private static Test02 getInstance() {
    6. synchronized(Test02.class) {
    7. if (instance == null) {
    8. instance = new Test02();
    9. }
    10. }
    11. return instance;
    12. }
    13. public static void main(String[] args) {
    14. Test02 one = Test02.getInstance();
    15. Test02 two = Test02.getInstance();
    16. System.out.println(one == two); // true
    17. }
    18. }

    但是这个方法还有一个小的缺点,那就是每次我们获取实例的时候,都需要加锁,这意味着性能的损失,当instance不为null的时候,已经不会由于多线程而产生问题了,也就不用加锁了,所以再次修改getInstance(),产生了第三个版本

    1. public class Test03 {
    2. private static Test03 instance;
    3. private Test03() {
    4. }
    5. private static Test03 getInstance() {
    6. if (instance == null) {
    7. synchronized(Test03.class) {
    8. if (instance == null) {
    9. instance = new Test03();
    10. }
    11. }
    12. }
    13. return instance;
    14. }
    15. public static void main(String[] args) {
    16. Test03 one = Test03.getInstance();
    17. Test03 two = Test03.getInstance();
    18. System.out.println(one == two); // true
    19. }
    20. }

    在这个版本中,只有在instancenull的时候,我们才加锁。

    上面的方法已经比较好了,这里再次推荐更好的办法,我们在instance声明的时候就为它赋值

    1. public class Test04 {
    2. // 在声明的时候就赋值
    3. private static Test04 instance = new Test04();
    4. private Test04() {
    5. }
    6. public static Test04 getInstance() {
    7. return instance;
    8. }
    9. public static void main(String[] args) {
    10. Test04 one = Test04.getInstance();
    11. Test04 two = Test04.getInstance();
    12. System.out.println(one == two); // true
    13. }
    14. }

    但是由于instance是静态变量,静态变量在类被主动使用时就会被初始化,而不是等我们需要创建类的实例时才被初始化,简单的说就是创建的时机过早,从而降低内存的使用效率,我们使用静态内部类来解决按需加载的问题

    1. public class Test05 {
    2. private Test05() {
    3. }
    4. private static final class InnerTest {
    5. private static Test05 instance = new Test05();
    6. }
    7. public static Test05 getInstance() {
    8. return InnerTest.instance;
    9. }
    10. public static void main(String[] args) {
    11. Test05 one = Test05.getInstance();
    12. Test05 two = Test05.getInstance();
    13. System.out.println(one == two); //true
    14. }
    15. }

    这个时候,只有我们需要用到类的实例时,才会初始化instance

    在上面5种实现单例模式的方法中:

    • 第一种方法在多线程的环境下不能工作
    • 第二种模式虽然能够在多线程的环境下工作,但是效率很低
    • 第三种方法通过两次判断确保能够在多线程的情况下高效的工作
    • 第四种方法在声明时就初始化,且只会被初始化一次,确保只创建一个实例
    • 第五种方法利用内部类,做到只要在真正需要的时候才会创建实例,提高空间使用效率