前言
想一想,如果公司里面有一个大牛,一个人就能把所有的事情都干了,那该多好!作为一个不喜欢卷来卷去的中国时代打工人,我喜欢这样的同事。作为吹着**春风成长起来的企业家,同样喜欢这样的员工(可以少聘用几个打工人了!!!可以裁掉几个打工人了!!!),可以省下不少成本!!!
如你所愿,单例模式就是这么一个时代楷模,虽然不能包揽所有工作,但是分内之事,一人足矣!接下来,来看一个简单的实例吧。
实例一:
package com.elean.design.singleton;
/**
* @auther Elean
* @date 17/7/2024 下午4:23
* @description
*/
public class SuperMan {
public static SuperMan tom = new SuperMan();
//拒绝招聘
private SuperMan() {
}
//有事找Tom
public static final SuperMan getInstance() {
return tom;
}
public void serveTea() {
System.out.println("端茶,我来");
}
public void pourWater() {
System.out.println("倒水,我来");
}
}
在这个案例里面,我们可以看到单例的几个最基本特点:
1)构造函数私有(拒绝招聘新人,Tom is super man)
2)有一个类变量来承载单一实例
3)给一个静态方法获取实例
当然,如果老板突然有一天喜欢上骑马、烧柴,就需要老实肯干的Tom另外学习喂马、劈柴的技能,如果没涨工资,这就对Tom是很不公平了(也违背了开闭原则:对扩展开放,对修改关闭)。这时候,就需要老板再开放一个岗位主管所有部门的马和柴,为了进一步节约成本,老板决定,招柴买马那天再设此岗位。
实例二:
package com.elean.design.singleton;
/**
* @auther Elean
* @date 17/7/2024 下午4:23
* @description
*/
public class BatMan {
public static BatMan xiaoMing;
private BatMan() {
}
public static final BatMan getInstance() {
if (null == xiaoMing) {
xiaoMing = new BatMan();
}
return xiaoMing;
}
public void horseFeed() {
System.out.println("喂马,我去");
}
public void choppingFirewood() {
System.out.println("劈柴,我去");
}
}
一、单线程下的使用及存在的问题
1.1 饿汉模式和懒汉模式
以上两个示例是单例模式中两种比较简单的写法:饿汉模式和懒汉模式
对比两种写法来看,饿汉模式在类的初始化过程会自动调用它的实例化方法给Tom赋值,而后一直存在于内存中;懒汉模式则是在调用该类的instance()方法时才会进行实例化。从表面上看,懒汉模式对资源的占用相对更小一些。但是我们知道,用户自定义类只有在被使用,或者子类被加载的时候才会被加载,如果我们能够做到让类的加载时间跟使用实例的时间一致,那么这种消耗是可以避免的,这就要求我们熟练掌握jvm的类加载机制,小心使用饿汉模式了。
1.2 反射破坏单例
在单线程中,一般情况下上面两个简单示例是完全可以保证内存中单例了。当然,如果我们使用反射把构造函数设置成true同样可以在内存中创建实例,这种情况,可以在构造函数中像懒汉模式一样先判断内存中是否有实例存在,如下代码。
package com.elean.design.singleton;
import java.lang.reflect.Constructor;
/**
* @auther Elean
* @date 18/7/2024 下午8:40
* @description
*/
public class SingleReflect {
public static void main(String[] args) {
SuperMan instance = SuperMan.getInstance();
try {
Class<?> clazz = Class.forN