单例模式属于创建型模式,是最简单的一种设计模式。当一个类在程序中只需要创建唯一全局对象时(如网站计数类、日志管理类、线程池类……),就可以使用单例模式。单例模式规定一个类只能创建一个实例,之后不能再创建新的实例。所有线程无论何时获取的都是该类的唯一对象,这样做节省了创建一个新对象的资源开销。
文章目录
- 单例模式的使用
- 单例模式类图
- 实现方法
- 第一步,创建网站计数器类(单例类)
- 网站计数器
- 第二步,创建测试类测试
- 测试类
- 实现效果
- 单例模式的几种实现方法
- 线程安全的懒汉加载方法
- 演示的单例类
- 线程不安全的懒汉加载方法
- 演示的单例类
- 饿汉式加载方法
- 演示的单例类
- 双检锁加载方法
- 演示的单例
- 静态内部类登记方法
- 演示的单例类
- 枚举型加载方法
- 演示的单例类
- 测试类
- 测试效果
单例模式的使用
单例模式保证一个类只存在一个对象,并提供访问该对象的静态方法,为了防止该类被多次创建,需要将它的构造方法设置为私有的。
单例模式类图
实现方法
第一步,创建网站计数器类(单例类)
网站计数器
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("当前网站点击量为:"+计数.获取点击数());
}
}