定义
单例模式(单件模式)确保一个类只有一个实例,并提供一个全局访问点。——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运行结果:
线程1第2次访问
线程2第1次访问
线程1第3次访问
线程2第4次访问
线程1第5次访问
线程2第6次访问
线程1第7次访问
线程2第8次访问
线程1第9次访问
线程2第10次访问
在懒汉下的多线程案例
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
同上
输出结果:
线程1第1次访问
线程2第2次访问
线程1第4次访问
线程2第3次访问
线程1第6次访问
线程2第5次访问
线程1第7次访问
线程2第8次访问
线程1第9次访问
线程2第10次访问
如果上述getInstance不添加synchronized,则会造成输出:
线程1第1次访问
线程2第1次访问
线程1第2次访问
线程2第2次访问
线程1第3次访问
线程2第3次访问
线程1第4次访问
线程2第4次访问
线程1第5次访问
线程2第5次访问
—————————————
上述懒汉下的多线程案例虽然达到预想效果但是存在缺陷:为了防止多个线程创建多个对象,给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/