Java设计模式之一 单例设计模式

本文介绍了设计模式的概念,源自建筑行业的经验总结,被应用于计算机编程以提高代码复用性和可维护性。重点讲解了Java中的单例模式,其特点是确保类只有一个实例,并提供全局访问点。文章探讨了单例模式的动机,如节省内存资源和确保对象唯一性,并阐述了解决问题的思路。文中详细展示了两种单例模式的实现方式:饿汉式和懒汉式(双重校验锁),并举例说明了单例模式在保证对象唯一性方面的效果。
摘要由CSDN通过智能技术生成

1、什么叫设计模式:

设计模式的概念首先来源于其它行业:建筑业,在早起建房子的时候,肯定是经验缺乏、显得杂乱无序的,这就会造成很多问题,在行业发展过程,通过不断的经验积累,前辈们针对这些问题提出了合理解决方案,这就是设计模式,参照设计模式往往可以解决很多问题,在计算机编程方面,也会出现类似问题,所以牛人们把这些解决问题的方案进行归类和总结,形成了面向对象编程的23种设计模式。

2、单例模式(特点):

Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”通过使用设计模式可以让我们的代码复用性更高,可维护性更高,让你的代码写的更优雅。

3、动机(单例模式的由来):

1、首先对象可以看着类的实体化副本,就像图纸和图纸所打造的实体,同一个类的不同对象,是属性的差异,但是有时候我们并不需要内存中存在该类多个对象,比如一个系统只能有一个窗口管理器或文件系统,如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要;再比如软件的配置文件有多个用户去获取配置信息的时候,如果也是创建多个对象,他们是分别操作不同的对象,数据是不能实时共享的,而如果内存是一个对象,则可以解决这个问题。

2、如果一个类的对象有可能在内存出现大量重复的时候,某种程度限制对象的创建,可以减少内存的消耗,当然这是一个比较牵强的动机

4、思路(解决问题的历程):

解决如上问题,其实首要想到的方法就是通过给类中成员添加静态(static)修饰的方式,这样就形成了数据的共享,但是这里存在的主要的问题就是静态成员在内存的驻留时间过长,至少比对象长,所有通过唯一对象的方式更加适合。

  • 如何保持对象的唯一性:

           1、不允许其它类中创建(new)该类的对象,因为这是无法控制创建数量的。

           2、必须在该类中提供一个该类的唯一实例。

           3、对外提供一个方法,以便其它类可以获取该实例。

具体办法:

           第一点:不允许其它类中创建(new)该类的对象,因对象的创建必须调用构造函数,所以把默认构造函数私有化。

           第二点:可以在类中创建(new)本类的对象。

           第三点:构建的一个方法,返回该类的对象。



代码历程:

public class Singleton {
	private Singleton(){};//满足第一点
	
	public Singleton getInstance(){//似乎满足第二点
		return new Singleton;//似乎满足第三点
	}
}

问题:为什么第二点和第三点加上似乎呢?仔细思考一下就会发现问题:

  1.  第二点中,因为该类不能创建对象,所以getInstance()无法调用;
  2.  第三点中,假如getInstance()可以调用,但每一次调用都会创建一个新的对象,不符合要求。
解决办法:
  1. 给方法加上静态修饰(static),通过类名调用。
  2. 创建一个对象的引用变量,指向该类的对象,在getInstance()中返回对象的引用。
修改后:
public class Singleton {
	Singleton instance = new Singleton();
	private Singleton(){};
	public static Singleton getInstance(){
		return instance;
	}
}
问题:
  1. 在静态方法getInstance中是无法调用非静态成员变量的,所以需要把instance变成静态成员。
  2. 如第一条成立,可以通过Singleton.instance获取到对象的实例,这样等效于调用getInstance方法,所以最好加上私有修饰符,禁止该途径调用对象实例。
一、正确代码(饿汉式):
public class Singleton {
	private static Singleton instance = new Singleton();
	private Singleton(){};
	public static Singleton getInstance(){
		return instance;
	}
}

这个时候我们以为掌握了单例模式,其实是掌握了,但仅仅是单例设计模式的一种写法,如上写法称为”饿汉式“,因instance是静态成员变量,所以在jvm对该类加载过程中,就进行对象的实例化,所以是提前创建实例。

另外一种写法,就是在getInstance调用时才进行对象的创建,这种写法又称为”延迟加载“或者”懒汉式“。

二、懒汉式(延迟加载)。
public class Singleton {
	private static Singleton instance = null;
	private Singleton(){};
	public static Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		}
		return instance;
	}
}

问题:懒汉式的写法,在多线程中会出现问题,即创建多个对象,如下图所示:

public class Singleton {
	private static Singleton instance = null;
	private Singleton(){};
	public static Singleton getInstance(){
		if(instance == null){
			--> 一个A线程进入并挂起。
			--> 此时一个B线程进入,这种情况会造成两个线程都会执行new Singleton操作,这样就会在内存存在多个该类对象。
			instance = new Singleton();
		}
		return instance;
	}
}

解决方案:加锁。

三、懒汉式同步锁(懒汉式线程不安全的解决方案)。
public class Singleton {
	private static Singleton instance = null;
	private Singleton(){};
	public static synchronized Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		}
		return instance;
	}
}

另一种写法:

public class Singleton {
	private static Singleton instance = null;
	private Singleton(){};
	public static  Singleton getInstance(){
		synchronized(Singleton.class){ //同步代码块,锁不能为this,因为该方法是静态方法,不能获取到this对象。
			if(instance == null){
				instance = new Singleton();
			}
			return instance;			
		}

	}
}

问题:每次调用getInstance时都会判断锁,这样会降低程序的效率。注:锁( Singleton.class )该类所属的字节码文件对象。

四、懒汉式双重校验锁。

public class Singleton {
	private static Singleton instance = null;
	private Singleton(){};
	public static  Singleton getInstance(){
		if(instance == null){
			synchronized(Singleton.class){ 
				if(instance == null){
					instance = new Singleton();
				}
				return instance;			
			}			
		}

	}
}
优点:只有第一次调用getInstance时,即instance等于null时才会判断锁,这样就提搞了效率。


5、单例模式的使用:
如果仅仅如上面所示的方式 ,去创建这些代码是没有意义,正确使用单例模式的方式就是在原有代码上,加上这些代码。

添加单例模式前:

public class Test {
	public static void main(String[] args){
		Person p1 = new Person();
		p1.setName("Jack");
		p1.setAge(18) ;
		Person p2 = new Person();
		p2.setName("David");
		p2.setAge(20);
		
		System.out.println(p1.getName() + ":" + p1.getAge());
		System.out.println(p2.getName() + ":" + p2.getAge());
	}
}

class Person{
	private String name ;
	private int age;
		
	public void setName(String name){
		this.name = name;
	}
	public String getName(){
		return name;
	}
	
	public void setAge(int age){
		this.age = age;
	}
	public int getAge(){
		return age;
	}
	
}

运行结果:

Jack:18
David:20

单例模式后:

public class Test {
	public static void main(String[] args){
		Person p1 = Person.getInstance();
		p1.setName("Jack");
		p1.setAge(18) ;
		Person p2 = Person.getInstance();
		p2.setName("David");
		p2.setAge(20);
		
		System.out.println(p1.getName() + ":" + p1.getAge());
		System.out.println(p2.getName() + ":" + p2.getAge());
	}
}

class Person{
	private String name ;
	private int age;
	
	private static Person instance = new Person();//2.本类中创建该类的对象
	private Person(){};//1.私有构造函数
	public static Person getInstance(){//3.创建一个方法,向外部提供该类的对象
		return instance;
	}
	
	public void setName(String name){
		this.name = name;
	}
	public String getName(){
		return name;
	}
	
	public void setAge(int age){
		this.age = age;
	}
	public int getAge(){
		return age;
	}
	
}

运行结果:

David:20
David:20

目的保证了对象的唯一性

6、参考:

1、毕向动老师的视频:JavaSE基础视频08 -【04-面向对象(单例设计模式-概述&体现).avi】、【05-面向对象(单例设计模式-内存图解).avi】、【06-面向对象(单例设计模式-懒汉式).avi】、传智播客毕向东Java基础视频教程-day11-14-多线程(多线程-单例设计模式-懒汉式)

2、百度百科:单例模式

3、stormzhang博客:设计模式之单例模式Android设计模式之单例模式单例模式的一些注意点


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值