单例模式属于创建型模式,是最简单的一种设计模式。当一个类在程序中只需要创建唯一全局对象时(如网站计数类、日志管理类、线程池类……),就可以使用单例模式。单例模式规定一个类只能创建一个实例,之后不能再创建新的实例。所有线程无论何时获取的都是该类的唯一对象,这样做节省了创建一个新对象的资源开销。
单例模式保证一个类只存在一个对象,并提供访问该对象的静态方法,为了防止该类被多次创建,需要将它的构造方法设置为私有的。

package 设计模式.单例模式;public class 网站计数器 {// 静态属性,这里使用的饿汉式实现单例private static 网站计数器 唯一实例 = new 网站计数器();private int 网站点击数;// 将该类的构造方法设置为私有,导致无法在外部创建对象private 网站计数器(){ }// 获取唯一实例的静态方法public static 网站计数器 获取唯一实例(){return 唯一实例;}public void 点击数增加(){this.网站点击数 += 1;}public int 获取点击数(){return this.网站点击数;}}
package 设计模式.单例模式;public class 测试类 {public static void main(String[] args) {网站计数器 计数器 = 网站计数器.获取唯一实例();计数器.点击数增加();System.out.println("当前网站点击量为:"+计数器.获取点击数());网站计数器 计数器二 = 网站计数器.获取唯一实例();计数器二.点击数增加();计数器二.点击数增加();计数器二.点击数增加();System.out.println("当前网站点击量为:"+计数器.获取点击数());网站计数器 计数器三 = 网站计数器.获取唯一实例();计数器三.点击数增加();计数器三.点击数增加();System.out.println("当前网站点击量为:"+计数器.获取点击数());}
}

单例模式的实现方法从线程安全以及资源消耗方面来讲,有线程安全懒汉式、线程不安全懒汉式、饿汉式、双检锁、静态内部类登记式和枚举式六种
此方法以懒加载的形式创建实例,当第一次需要使用该对象时才会创建实例,以此来避免浪费内存。同时,此方法使用synchronzed 同步锁来实现线程安全,保证多个线程同时调用时只会创建单个实例,但加锁难免损耗一些性能。
package 设计模式.单例模式;public class 网站计数器 {/* 线程安全懒汉式创建单例 */// 用静态属性存储单例对象private static 网站计数器 唯一实例 ;private int 网站点击数;// 将该类的构造方法设置为私有,导致无法在外部创建对象private 网站计数器(){ }// 获取唯一实例的静态方法public static synchronized 网站计数器 获取唯一实例(){// 不存在唯一实例时先创建实例if (唯一实例 == null){唯一实例 = new 网站计数器();}return 唯一实例;}public void 点击数增加(){this.网站点击数 += 1;}public int 获取点击数(){return this.网站点击数;}}
此方法与上面线程安全的懒汉加载方法基本一样,但是此方法没有添加 sycnhronized 同步锁,所以在多线程同时请求时,可能会导致创建多个对象来覆盖之间的实例对象。
public class 网站计数器 {/* 线程不安全的懒汉式创建单例 */// 用静态属性存储单例对象private static 网站计数器 唯一实例 ;private int 网站点击数;// 将该类的构造方法设置为私有,导致无法在外部创建对象private 网站计数器(){ }// 获取唯一实例的静态方法public static 网站计数器 获取唯一实例(){// 不存在唯一实例时先创建实例if (唯一实例 == null){唯一实例 = new 网站计数器();}return 唯一实例;}public void 点击数增加(){this.网站点击数 += 1;}public int 获取点击数(){return this.网站点击数;}}
这种方法最为简单,同时也是线程安全的。但由于在类加载时就进行了初始化操作占据一部分内存空间,如果要过很久才会调用这个类的话,那么此类就是占着茅坑不拉屎,浪费了程序的内存空间。
package 设计模式.单例模式;public class 网站计数器 {/* 饿汉式创建单例 */// 静态属性,这里使用的饿汉式实现单例private static 网站计数器 唯一实例 = new 网站计数器();private int 网站点击数;// 将该类的构造方法设置为私有,导致无法在外部创建对象private 网站计数器(){ }// 获取唯一实例的静态方法public static 网站计数器 获取唯一实例(){return 唯一实例;}public void 点击数增加(){this.网站点击数 += 1;}public int 获取点击数(){return this.网站点击数;}}
此方法优化了线程安全的懒汉式加载方法,它取消了获取实例方法上的synchronized同步锁,让多个线程获取实例时不需要排队获取,损耗性能。双检锁使用两重检查,先判断实例对象是否存在,如果已经存在则直接返回实例,而不存在实例时再通过同步锁保证线程安全的创建唯一实例。
package 设计模式.单例模式;public class 网站计数器 {/* 双检锁创建单例 */// 用静态属性存储单例对象private static 网站计数器 唯一实例;private int 网站点击数;// 将该类的构造方法设置为私有,导致无法在外部创建对象private 网站计数器(){ }// 获取唯一实例的静态方法public static 网站计数器 获取唯一实例(){// 如果唯一实例已经创建则直接返回实例if (唯一实例 == null){// 多个线程同时准备创建实例时,使用同步锁synchronized(网站计数器.class){// 如果有线程已经创建了实例,那此线程就不再创建了if (唯一实例 == null){唯一实例 = new 网站计数器();}}}return 唯一实例;}public void 点击数增加(){this.网站点击数 += 1;}public int 获取点击数(){return this.网站点击数;}}
该方法利用Classloader的加载机制,当类被加载时,并不会加载它的内部类,只有当显式调用该内部类时,该内部类才会被加载。通过该机制,可以实现一种线程安全的懒汉式方法创建单例对象。
package 设计模式.单例模式;public class 网站计数器 {/* 静态内部类创建单例 */private int 网站点击数;static class 内部类{private static final 网站计数器 唯一单例 = new 网站计数器();}public static final 网站计数器 获取唯一实例(){// 只有外部显示调用此方法时,内部类才会被加载,唯一单例对象才会被创建return 内部类.唯一单例;}public void 点击数增加(){this.网站点击数 += 1;}public int 获取点击数(){return this.网站点击数;}}
枚举是在jdk1.5后加入的,所以使用的人不是很多,但它却是最为便捷与简单的一种线程安全的单例创建方法。其本质是利用枚举值的唯一性与自动支持序列化的机制,杜绝在反序列化时创建新的对象。
package 设计模式.单例模式;public enum 枚举类网站计数器 {唯一实例;private int 网站点击数;public void 点击数增加(){this.网站点击数 += 1;}public int 获取点击数(){return this.网站点击数;}
}
package 设计模式.单例模式;public class 测试类 {public static void main(String[] args) {枚举类网站计数器 计数 = 枚举类网站计数器.唯一实例;System.out.println("当前网站点击量为:"+计数.获取点击数());计数.点击数增加();System.out.println("当前网站点击量为:"+计数.获取点击数());枚举类网站计数器 计数二 = 枚举类网站计数器.唯一实例;计数二.点击数增加();计数二.点击数增加();计数二.点击数增加();System.out.println("当前网站点击量为:"+计数二.获取点击数());枚举类网站计数器 计数三 = 枚举类网站计数器.唯一实例;计数三.点击数增加();计数.点击数增加();计数三 = 计数二;计数三.点击数增加();System.out.println("当前网站点击量为:"+计数.获取点击数());}
}
