【设计模式】单例模式Singleton(Java)

文章探讨了Java中实现单例模式的两种经典方式——懒汉模式和饿汉模式,以及它们在多线程环境下的应用。懒汉模式在多线程下可能产生多个实例,而饿汉模式则在类加载时即创建实例。为解决懒汉模式的性能问题,文章提出了双重检查加锁的优化策略,通过volatile关键字和synchronized同步块减少不必要的同步。
摘要由CSDN通过智能技术生成

定义

单例模式(单件模式)确保一个类只有一个实例,并提供一个全局访问点。——HeadFirst

单例模式通过过防止外部实例化和修改来控制创建的对象的数量。

关键点:

  • 私有构造函数,没有其他类可以实例化一个新对象。
  • 私有引用-没有外部修改。
  • Public static getInstance方法是唯一可以获取对象的地方。

问:为什么单例模式能确保实例独一无二?
答:因为单例模式没有公开的构造器,构造器是私有的,因此他人无法自己创建实例对象。而且他人想获取实例必须通过调用getInstance静态方法得到实例,实例也许是调用时创建的,也许是之前已经创建好的。

类图

在这里插入图片描述

Java经典实现

私有构造器、一个静态方法、一个静态变量。

懒汉Lazy Mode:

public class Singleton{
    // 使用一个静态变量记录Stingleton的唯一实例
    private static Singleton uniqueInstance;
    // 这里是其他有用的实例化变量
    // 私有构造方法
    private Singleton(){} 

    public static Singleton getInstance(){
        if(uniqueInstance == null){
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
    // 其他有用的方法
}

饿汉Eager Mode:

public class Singleton2 {
    // 使用一个静态私有变量记录Stingleton的唯一实例
    private static Singleton2 uniqueInstance = new Singleton2();
    // 这里是其他有用的实例化变量
    // 私有构造方法
    private Singleton2(){}

    public static Singleton2 getInstance(){
        return uniqueInstance;
    }
    // 其他有用的方法
}

在饿汉下的多线程案例

单例类GlobalNum.java

public class GlobalNum {
     private static  GlobalNum gn=new GlobalNum();
     private int num=0;
     public static GlobalNum getInstance()
     {
    	 return gn;
     }
     public synchronized int getNum() //加锁
     {
    	 return ++num; //返回访问次数
     }
}

主方法SingleMain.java

class NumThread extends Thread{
	private String threadName;
	public NumThread(String name)
	{
		threadName=name;
	}
	public void run()
	{ 
		GlobalNum gnObj=GlobalNum.getInstance();
		for (int i = 0; i < 5; i++) {
			System.out.println(threadName+"第"+gnObj.getNum()+"次访问");
			try {
				this.sleep(1000);
			} catch (Exception e) {
				// TODO: handle exception
				System.out.println("错误");
			} 
		}
	}
}

public class SingleMain {
//测试单件模式
	public static void main(String[] args) {
		// TODO Auto-generated method stub
        NumThread thread1=new NumThread("线程1");
        NumThread thread2=new NumThread("线程2");
        thread1.start();
        thread2.start();
	}
}

JDK8运行结果:

线程12次访问
线程21次访问
线程13次访问
线程24次访问
线程15次访问
线程26次访问
线程17次访问
线程28次访问
线程19次访问
线程210次访问

在懒汉下的多线程案例

getInstance方法添加synchronized关键字,防止不同线程创建多个实例对象从而违反单例模式。

public class GlobalNum {
     private static GlobalNum globalNum;
     private int num=0;
     // getInstance方法添加synchronized关键字,防止创建多个实例
     public static synchronized GlobalNum getInstance()
     {
    	 if(globalNum==null){
             globalNum = new GlobalNum();
             return globalNum;
         }
         return globalNum;
     }
     public synchronized int getNum() //加锁
     {
    	 return ++num; //返回访问次数
     }
}

主方法SingleMain.java

同上

输出结果:

线程11次访问
线程22次访问
线程14次访问
线程23次访问
线程16次访问
线程25次访问
线程17次访问
线程28次访问
线程19次访问
线程210次访问

如果上述getInstance不添加synchronized,则会造成输出:

线程11次访问
线程21次访问
线程12次访问
线程22次访问
线程13次访问
线程23次访问
线程14次访问
线程24次访问
线程15次访问
线程25次访问

—————————————
上述懒汉下的多线程案例虽然达到预想效果但是存在缺陷:为了防止多个线程创建多个对象,给getInstance添加synchronized,但实际上只有第一次执行getInstance方法,才需要真正的同步,当已经创建好对象时,后续无需在同步getInstance方法(导致后续每次调用getInstance,同步变成累赘,性能资源浪费)。

改进:使用双重检查加锁,减少使用同步。

添加volatile及synchronized同步块

public class GlobalNum {
     private static volatile GlobalNum globalNum;
     private int num=0;
     public static  GlobalNum getInstance()
     {
    	 if(globalNum==null){
             synchronized(GlobalNum.class){
                 if(globalNum==null)
                     globalNum = new GlobalNum();
             }
         }
         return globalNum;
     }
     public synchronized int getNum() //加锁
     {
    	 return ++num; //返回访问次数
     }
}

总结

  • 单例模式确保一个程序中的一个类只有一个实例。
  • 单例模式提供访问这个实例的全局访问点。
  • Java实现单例模式:私有构造器、一个静态变量、一个静态方法。
  • 确定性能资源的限制后,在多线程情况下,谨慎选择合适的方法实现单例模式。

参考:
HeadFirst设计模式、https://www.programcreek.com/2011/07/java-design-pattern-singleton/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值