JAVA笔记整理(十二),JAVA中的单例模式

我们在写程序的时候,大部分时候,会把类定义成为public类型的,那么任何类都可以随意的创建该类的对象。但是有时候,这种做法并没有任何意义,频繁的创建对象和回收对象造成内存损耗,所以就有了单例模式。

一个类只能创建一个对象,则这个类被成为单例类,这种模式被成为单例模式。

单例模式的原则是:

  • 把类的构造方法隐藏起来
  • 创建一个方法,这个方法可以创建一个(并且只有一个)自己的实例
  • 这个方法可以被外部使用

单例模式分为:懒汉模式、饿汉模式和等级模式

懒汉模式:

/**
     * 懒汉模式,特征是待到使用时,才创建该类对象
     */

public class Single {

    // 定义一个类变量,来存储该类对象
    private static Single single;

    // 隐藏构造器
    private Single() {

    }

    // 定义一个方法供外部调用,返回一个该类唯一的对象
    public static Single getSingle() {
        if (single == null) {
            single = new Single();
        }
        return single;
    }

    public static void main(String[] args) {
        Single s1 = Single.getSingle();
        Single s2 = Single.getSingle();
        if (s1 == s2) {
            System.out.println("Single类是单例模式");
        } else {
            System.out.println("Single类不是单例模式");
        }
    }
}

饿汉模式:

/**
     * 饿汉模式 饿汉模式特征是在类加载时候已经创建好实例待调用,并且该实例不可变
     */

public class Single {
    
    // 直接创建该类的实例,该实例不可变
    private static final Single single = new Single();

    // 隐藏构造函数
    private Single() {

    }

    // 创建一个方法供外部调用,返回该类的实例
    public static Single getSingle() {
        return single;
    }

    public static void main(String[] args) {
        Single s1 = Single.getSingle();
        Single s2 = Single.getSingle();
        if (s1 == s2) {
            System.out.println("Single类是单例模式");
        } else {
            System.out.println("Single类不是单例模式");
        }
    }
}

登记模式:

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

     /**
     * 单例模式之登记模式,特征是事先维护一组map存放一个实例,当创建实例时,先查看map是否存在,如果存在,则直接返回,如果不存在,则先存入,再返回
     */
public class Single {
    
    // 定义一个map,用来登记实例
    private static Map<String, Single> map = new HashMap<String, Single>();
    // 先在map中存放一个Single实例
    static {
        Single single = new Single();
        map.put(single.getClass().getName(), single);
    }

    // 隐藏构造器
    private Single() {

    }

    // 定义一个方法供外部调用,用来返回实例,需要传入一个参数作为map中的“键”
    public static Single getSingle(String key) {
        // 如果传入的参数为null,则讲类名称赋值给它
        if (key == null) {
            key = Single.class.getName();
        }
        // 根据传入的“键”判断是否存在“值”,如果不存在,则将该
        if (map.get(key) == null) {
            try {
                map.put(key, (Single) Class.forName(key).newInstance());
            } catch (InstantiationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return map.get(key);
    }

    public static void main(String[] args) {
        Single single1 = Single.getSingle(null);
        Single single2 = Single.getSingle(null);
        System.out.println(single1 == single2);
    }
}

单例中的线程安全问题

饿汉式这种一开始就创建实例的方式,线程是安全的,懒汉式则不同,看代码

public class Single {

    // 定义一个类变量,来存储该类对象
    private static Single single;

    // 隐藏构造器
    private Single() {

    }

    // 定义一个方法供外部调用,返回一个该类唯一的对象
    public static Single getSingle() {
        if (single == null) {  //当线程执行到这里挂起时,别的线程获取执行权限执行到这里,还会继续给single创建实例,这样就造成了线程安全问题。
            single = new Single();
        }
        return single;
    }

    public static void main(String[] args) {
        Single s1 = Single.getSingle();
        Single s2 = Single.getSingle();
        if (s1 == s2) {
            System.out.println("Single类是单例模式");
        } else {
            System.out.println("Single类不是单例模式");
        }
    }
}

为了避免懒汉式的线程安全问题,我们需要给他加上代码同步

public class ThreadForSingle {
    public static void main(String[] args) {

    }
}

class Single {

    private Single() {
    }

    private static Single single = null;

    public static Single getSingle() {

        synchronized (Single.class) {
            if (single == null) {
                single = new Single();
            }
        }
        return single;
    }
}

当线程进来判断出single为null时,遇到问题挂起,其余的线程则无法进来,这样就保证了线程的安全