单例模式定义
单例模式定义就是确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。属于设计模式的创建型模式。
单例模式有3个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例
单例模式结构和实现
单例模式结构
只包含一个类,即单例类。
对于Singleton,在类内创建唯一实例,通过静态方法GetInstance()让客户端可以使用它唯一实例;且为了防止被外部对单例类实例化,将单例类的构造函数访问权限设为private;在单例类内定义一个Singleton类型的静态对象供外部共享访问的唯一实例。
静态变量与非静态变量
被static关键字修饰的变量,叫类变量或静态变量;没有static修饰的是成员变量。
类的静态变量
- 在内存中只有 一个,java虚拟机在类加载过程中为静态变量分配内存。
- 静态变量位于方法区,被类的所有实例所共享。
- 静态变量可通过类名来访问,其生命周期取决于实例的生命周期。
实例变量
- 取决于类的实例。每创建一个实例,java虚拟机会为实例变量分配一次内存。
- 实例变量位于堆中;其生命周期取决于实例的生命周期。
单例模式的实现
public class Singleton1 {
private static Singleton1 instance=null;//静态私有成员变量
//私有构造函数
private Singleton1(){}
//静态公有工厂方法,返回唯一实例
public static Singleton1 getInstance(){
if(instance==null)
instance=new Singleton1();
return instance;
}
}
测试代码:
public class Client {
public static void main(String[] args) {
Singleton1 s1=Singleton1.getInstance();
Singleton1 s2=Singleton1.getInstance();
if(s1==s2){
System.out.println("两个对象是相同实例");
}else{
System.out.println("两个对象是不同实例");
}
}
}
编译运行结果如下:
两个对象是相同实例
由上可看出两次调用getInstance()所获取的对象是同一实例对象,并无法再外部对Singleton实例化。
在单例模式的实现过程中需注意:
- 构造函数的可见性为private。
- 提供一个类型为自身的静态私有成员变量。
- 提供一个公有静态工厂方法
饿汉式单例
类被加载时,静态变量会被初始化,类的私有构造函数会被调用,单例类的唯一实例被创建。
- 饿汉式:线程安全,调用效率高。示例:
public class Singleton {
//饿汉式
private static Singleton instance=new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
特点:
- 无需考虑多个线程同时访问的问题,可确保实例的唯一性。
- 从调用速度和响应时间来看,由于单例对象一开始就得以创建,因此优于懒汉式单例。
- 无论系统运行时是否需要该单例对象,由于在类加载时该对象就创建,资源利用率不及懒汉式单例。
- 系统加载时创建饿汉式单例对象,使得加载时间会较长。
懒汉式单例
懒汉式单例在第一次被引用时实例化,类加载时不会被实例化。
- 懒汉式:线程不安全。示例:
public class LazySingleton {
private static LazySingleton instance=null;
private LazySingleton(){}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
运行时加载对象,加载类较快,获取对象慢;线程不安全,可加synchronized关键字,但效率低。
- 懒汉式:双重校验锁。示例:
public class LazySingleton {
private static volatile LazySingleton instance=null;
private LazySingleton(){}
public static LazySingleton getInstance() {
//第一重判断
if (instance == null) {
//线程锁定,以处理多个线程同时访问的情况,线程安全
synchronized (LazySingleton.class){
//第二重判断
if(instance==null){
instance=new LazySingleton();
}
}
}
return instance;
}
}
- 第一个instance == null,提高效率:变量使用volatile关键字可以保证可见性。
- 第二个instance == null,为了保证单例:返回是同一个对象。
instance=new LazySingleton();
new对象分解三条指令:
1.分配内存空间
2.初始化对象
3.赋值给变量
假设不加volatile,但能保证变量保证可见性(不保证重排序):若线程A以1-3-2,且执行到3时,另外的线程执行到第一个判断时进不去之后的代码,直接返回,会发生错误
volatile作用:
- 禁止指令重排序
- 建立内存屏障
synchronized:
- 有序性指线程之间运行同步代码块,是按序执行的。
懒汉式特点:
- 实例在第一次使用时创建,无需一直占用系统资源,实现了延迟加载
- 必须处理多个线程同时访问的问题,特别是当单例类作为资源控制器
- 多线程同时引用此类时。需通过双重校验机制进行控制,导致系统性能受影响
单例模式优缺点
优点:
- 提供了对唯一实例的受控访问,能严格控制客户对实例的访问。
- 节约系统资源,提高系统的性能。
- 允许可变数目的实例。
缺点:
- 系统中只有一个实例,导致单例类职责过重,违背了“单一职责原则”。
- 没有抽象层,不利于单例类的扩展。
- 自动垃圾回收技术,若实例化的共享对象长时间不被利用,会自动销毁并回收资源,下次利用重新实例,导致单例对象状态的丢失。
适用环境
- 系统只需要一个实例对象,例如:序列号生成器、资源管理器。
- 客户调用类的单个实例值允许使用一个公共访问点,除了该访问点,不能通过其他途径访问该实例。
- 数据库连接池。
- Servlet这种Aplication