单例模式02:懒汉模式
正文开始@Assassin
1. 回顾饿汉模式:
简单回顾一下单例模式中的饿汉模式~
我是饿汉模式!
所谓饿汉模式是指:只要类被加载,此对象gf
就会被创建,即使不使用gf
,它也会被创建,这是类的加载机制:只要调用了静态方法或属性,该方法或属性所在的类就会被加载,类加载之后便会首先初始化静态属性和静态代码块。
举个俗点的例子,就说你还没跟gf
相处很久,你俩还没来得及相互了解它便成了你的女朋友,也就是操之过急,像个饥饿的汉子一样,这便是饿汉模式的通俗理解。
这边还是具体举个栗子:
package com.haut.iot.assassin;
//有一个类是GirlFriend
//假如你只能有一个女朋友
class GirlFriend {
private String name; //姓名
public static int n = 1; //静态变量
//如何保障我们只能创建一个GirlFriend对象?
//[单例模式 -> 饿汉模式]
//1. 构造器私有化
//2. 在类的内部创建对象(该对象需是static)
//3. 向外提供一个静态的公共方法
private static GirlFriend gf = new GirlFriend("王祖贤");//私有对象
//为了在getInstance()返回gf,需将该方法修饰为static
public static GirlFriend getInstance() {
return GirlFriend.gf; //返回gf
}
private GirlFriend(String name) { //构造器
System.out.println("我是构造器,我被调用了!");
this.name = name;
}
@Override
public String toString() { //重写toString()
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
public class SingleTon {
public static void main(String[] args) {
System.out.println(GirlFriend.n); //只使用了n这个静态变量,而没有使用gf对象
}
}
可以看到在类中定义了一个static
的静态变量n
此时在测试类中使用类变量GirlFriend.n
,我们应该知道,当使用到类变量时,类加载器便会执行类加载,类加载的第一步便是初始化静态变量和静态代码块,如下图:
于是 private static GirlFriend gf = new GirlFriend("王祖贤");
便会被执行,也就是创建了GirlFriend
对象。
相应的也会调用构造方法,验证如下:
可以看到构造器的确被调用了,但并没有使用到gf
对象,这便很好地验证了饿汉模式的弊端:没有使用的需求,对象也被创建了
2. 懒汉模式细节:
懒汉模式的大概实现结构如下:
- 构造器私有化
- 创建一个
static
静态对象引用,并不直接new
出来对象 - 提供一个
public
的static
方法,可以得到一个Cat
对象 - 懒汉模式只有当程序员使用
getInstance()
方法时才返回对象,后面再次调用时会返回原对象,从而保证了单例
看这样一段代码: 可以看到static
的对象引用并没有引用实例,也就是在此并没有创建对象。
也就是说此时即使类被加载,构造器也不会被调用,构造器的调用是伴随着对象的创建而调用的,它的作用是初始化已经创建好的对象。
同样的,我们来验证一下:
package com.haut.iot.ninghai;
//希望在程序运行过程中,只创建一个对象
//使用单例模式
class Cat {
private String name;//名字
private static Cat cat; //默认为null
public static int n = 100;//静态变量
//1.构造器私有化
//2.创建一个static静态对象
//3.提供一个public的static方法,可以得到一个Cat对象
private Cat(String name) {//构造器我们照样修饰成private
System.out.println("我是构造器,我被调用了~");
this.name = name;
}
//getInstance()方法
public static Cat getInstance() {
if (cat == null) {
cat = new Cat("小猫咪");
}
return cat;
}
}
//演示懒汉式的单例模式
public class SingleTon {
public static void main(String[] args) {
System.out.println(Cat.n);
}
}
在类中同样定义了一个public
的static
类变量n
在测试类中通过类名来调用n
运行结果如下:
能清楚的看到这是输出了n
的值100,并没有调用构造器打印该语句:
也就是说,此时虽然类已经被加载了,但是并没有创建对象,这跟饿汉模式有着本质上的区别,大伙们自己去对比一哈~
那我们什么时候才能创建对象呢?
自行调用一下getInstance()
就好了~~
如下图所示:
执行结果:
源代码:
package com.haut.iot.ninghai;
//希望在程序运行过程中,只创建一个对象
//使用单例模式
class Cat {
private String name;//名字
private static Cat cat; //默认为null
public static int n = 100;//静态变量
//1.构造器私有化
//2.创建一个static静态对象
//3.提供一个public的static方法,可以得到一个Cat对象
private Cat(String name) {//构造器我们照样修饰成private
System.out.println("我是构造器,我被调用了~");
this.name = name;
}
//getInstance()方法
public static Cat getInstance() {
if (cat == null) {
cat = new Cat("小猫咪");
}
return cat;
}
}
//演示懒汉式的单例模式
public class SingleTon {
public static void main(String[] args) {
System.out.println(Cat.n); //类变量
Cat instance = Cat.getInstance(); //得到一个Cat对象
}
}
为什么这样就好了呢?
我们来分析一波:
能清楚的看到,在getInstance()
里面逻辑是这样的:如果cat
为null
,就new
一个对象,如果不为空,直接返回原对象。因为cat
默认是为null
的,所以第一次调用getInstance()
时会创建一个Cat
对象,之后再调用就会返回源对象了,保证了一个类只有一个实例对象。
惯用套路,我们来实操一下:
package com.haut.iot.ninghai;
//希望在程序运行过程中,只创建一个对象
//使用单例模式
class Cat {
private String name;//名字
private static Cat cat; //默认为null
public static int n = 100;//静态变量
//1.构造器私有化
//2.创建一个static静态对象
//3.提供一个public的static方法,可以得到一个Cat对象
private Cat(String name) {//构造器我们照样修饰成private
System.out.println("我是构造器,我被调用了~");
this.name = name;
}
//getInstance()方法
public static Cat getInstance() {
if (cat == null) {
cat = new Cat("小猫咪");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
//演示懒汉式的单例模式
public class SingleTon {
public static void main(String[] args) {
//System.out.println(Cat.n); //类变量
Cat instance1 = Cat.getInstance(); //得到一个Cat对象
System.out.println(instance1);
Cat instance2 = Cat.getInstance();//单例模式,同一个对象
System.out.println(instance2);
//true
System.out.println(instance1 == instance2);
}
}
运行结果:
3. 懒汉模式VS饿汉模式:
饿汉式VS懒汉式
- 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
- 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题
- 在
javaSE
标准类中,java.lang.Runtime
就是经典的单例模式(饿汉模式)
简单分析一哈:
懒汉模式的线程安全问题主要体现在getInstance()
方法中。
假设同时有三个线程执行getInstance()
方法,这三个线程都进入了if
语句的判断中,当第一个线程判断为null
时,第二个线程也很快地执行了if
条件判断。 假设当第二个线程进到if中,第一个线程还没有创建好对象,那么第二个线程也会去创建新对象,因为此时判断为null
。 同理,第三个线程也是如此。这样一来就会造成同一时间点创建多个对象,显然这破坏了单例模式的规则。这个线程安全问题是可以解决的,以后再谈⑧
饿汉模式会造成资源的浪费是因为可能你的需求只是使用另一个模块,但是类的加载机制会把你不需要的工作给做好(创建对象),这样就造成了资源浪费,而懒汉式必须得自己调用才会创建对象,不存在浪费资源。
在JavaSE
中,java.lang.Runtime
中存在经典的单例模式,我们来看看源码:
很明显,这是一个饿汉式的单例模式。