文章目录
定义:
单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。
典型案例:
jdk 源码 Rutime类 —— 饿汉式单例模式
一、饿汉式 (线程安全)
1.1 静态变量
实现步骤:
1)构造器私有化;(防止通过 new 得到实例对象)
2)类内部创建对象;
3)对外提供获取改实例对象的公有方法。
public class Hunger{
//私有静态实例对象
private static final Hunger instance=new Hunger();
//私有构造器
private Hunger(){
}
//对外提供获取实例对象的方法
public static Hunger getInstance(){
return instance;
}
}
1.2 静态代码块
在静态代码块中实例化对象,其本质上和上面那种没什么分别。
public class Hunger{
//私有静态实例对象
private static Hunger instance;
//静态代码块
static{
instance=new Hunger();
}
//私有构造器
private Hunger(){
}
//对外提供获取实例对象的方法
public static Hunger getInstance(){
return instance;
}
}
优点:
在类加载时就完成了对象实例化,避免了线程同步问题(线程安全)。
缺点:
如果始终没用使用这个实例对象,则会造成内存空间的浪费。
二、懒汉式
2.1 线程不安全的懒汉式
实现步骤:
1)私有构造器;
2)内部声明一个对象,但是不实例化;
3)提供一个公有方法,实例化并返回该对象。
public class Lazyer{
//私有静态实例对象
private static Lazyer instance;
//私有构造器
private Lazyer(){
}
//对外提供获取实例对象的方法
public static Lazyer getInstance(){
if(instance==null){
instance=new Lazyer();
}
return instance;
}
}
优点:
使用时才实例化对象,避免造成内存浪费;
缺点:
线程不安全:因为如果多个线程同时进入if(instance == null)
,可能会同时满足条件,从而创建出多个实例对象出来。
总结:
实际开发中不使用这种方式,因为存在潜在风险。
2.2 线程安全的懒汉式
在上一种懒汉式条件下使用同步方法。
public class Lazyer{
//私有静态实例对象
private static Lazyer instance;
//私有构造器
private Lazyer(){
}
//对外提供获取实例对象的同步方法
public static synchronized Lazyer getInstance(){
if(instance==null){
instance=new Lazyer();
}
return instance;
}
}
优点:
解决了线程不安全的问题。
缺点:
效率太低,每个线程想要获得该实例对象时都要进行线程同步。
总结:
实际开发中尽量不用这种写法。
2.3 同步代码块懒汉式 (线程不安全)
public class Lazyer{
//私有静态实例对象
private static Lazyer instance;
//私有构造器
private Lazyer(){
}
//对外提供获取实例对象的同步方法
public static synchronized Lazyer getInstance(){
if(instance==null){
synchronized(Lazyer.class){
instance=new Lazyer();
}
}
return instance;
}
}
缺点:
本质上和 2.1 没什么区别,实际开发中不能使用这种写法(但面试可能会考!)。
三、双重检查
使用 volatile 修饰内部对象,带同步代码块内外都检测一次对象是否为空。
public class Lazyer{
//声明私有静态对象,用volatile修饰
private static volatile Lazyer instance;
//私有构造器
private Lazyer(){
}
//对外提供获取实例对象的方法
public static Lazyer getInstance(){
if(instance==null){
//同步代码块
synchronized(Lazyer.class){
if(instance==null){
instance=new Lazyer();
}
}
}
return instance;
}
}
优点:
线程安全、效率高、延迟加载。推荐使用这种单例设计模式。
四、静态内部类方式
内部类和静态内部类都是延时加载的,也就是说只有在明确用到内部类时才加载。
而且只会被加载一次,此时线程是安全的。
class Singleton{
//私有构造器
private Singleton(){}
//静态内部类
private static class SingletonInstance(){
private static final Singleton INSTANCE=new Singleton();
}
//获取实例的公有方法
public static Singleton getInstence(){
return SingletonInstance.INSTANCE;
}
}
优点:
避免了线程不安全;利用静态内部类实现延迟加载,节约内存;效率高。推荐在实际开发中使用。
五、枚举方式
创建一个枚举类,仅具有一个实例对象。
enum Singleton_e{
INSTANCE;
}
优点:
① 避免了多线程同步的问题;
② 防止通过反序列化和反射来创建对象。
推荐在实际开发中使用。
六、总结
1. 推荐使用的单例设计模式
① 静态内部类方式;
② 双重检查方式;
③ 饿汉模式;(如果确定对象会被使用)
④ 枚举方式。
2. 注意事项
① 单例模式保证了系统中某个类只存在一个实例对象,节省了系统资源,对于一些需要频繁创建和销毁的对象(重量级对象,频繁访问数据库或文件的对象),可以使用单例模式来设计;
② 实例化一个单例对象时,要使用对应的创建方法,而不是 new。