1.1 什么是单例模式?
内功这种东西,要融会贯通得多来点儿栗子。。
案例:我是皇帝,独此一家。
一个类只能产生一个对象(皇帝),大家对他的依赖都是相同 的。我们把皇帝这种特殊的职位通过程序来实现。
皇帝类: 注意 //构造方法私有,避免在类的外部创建对象
public class Emperor {
private static Emperor emperor=null;
//构造方法私有,避免在类的外部创建对象
private Emperor() { }
public static Emperor getInstance(){
if(emperor==null) {
emperor=new Emperor();
}
return emperor;
}
//皇帝办公
public void work(){
System.out.println("我是皇帝 hjg,有事起奏,无事退朝!");
}
}
大臣类: 调用 Emperor.getInstance() 生产同一个皇帝
public class Minister {
public static void main(String[] args) {
//每天见面的都是同一个皇帝
for (int i=1;i<=5;i++){
Emperor emperor = Emperor.getInstance();
System.out.print("第"+i+"天:");
emperor.work();
}
}
}
运行结果:
1.2 单例模式的定义以及特点 思考::
官方定义;
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
Ensure a class has only one instance,and provide a global point of access to it.
特点:
单例类只有一个实例对象;
该单例对象必须由单例类自行创建;
单例类对外提供一个访问该单例的全局访问点。
1.3 单例模式的分类
上面的单例模式,在低并发的情况下可能不会出现问题,如果并发量增大,内存中就会出现多个实例,就不是真正 意义上的单例。为什么会出现这种情况呢?
解决线程不安全的方式有多种。我们先将上面代码的单例模式修改为线程安全的:
懒汉式单例:
public class LazyEmperor {
//保证emperor在所有线程中同步
private static volatile LazyEmperor emperor=null;
//构造方法私有,避免在类的外部创建对象
private LazyEmperor() { }
public static synchronized LazyEmperor getInstance(){
if(emperor==null) {
emperor=new LazyEmperor();
}
return emperor;
}
//皇帝办公
public void work(){
System.out.println("我是皇帝 hjg,有事起奏,无事退朝!");
}
}
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。
ps: 关键字 volatile 和 synchronized,能保证线程安全
缺点:每次访问时都要同步,会影响性能,且消耗更多的资源
饿汉式单例:
public class HungryEmperor {
实例化一个皇帝
private static final HungryEmperor emperor = new HungryEmperor();
//构造方法私有,避免在类的外部创建对象
private HungryEmperor() { }
public static HungryEmperor getInstance(){
return emperor;
}
//皇帝办公
public void work(){
System.out.println("我是皇帝 hjg,有事起奏,无事退朝!");
}
}
该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。而且该方式是线 程安全的。
1.4 单例模式的使用场景
- 某类只要求生成一个对象的时候,如一个航班的机长、每个人的身份证号等。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问 速度。如 Web 中的配置对象、数据库的连接池等。
- 当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
- 在计算机系统中, Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、 打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应 用程序中的对话框、系统中的缓存等常常被设计成单例。
1.5 单例模式的优缺点
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用(比如写文件操作)。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问。
缺点:
- 单例模式一般没有接口,扩展很困难。如果要扩展,只能修改代码。 <开闭原则 冲突>
- 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
1.6 单例模式的扩展(双栗,多栗)
单例模式可扩展为有限的多例(Multitcm)模式,这种模式可生成有限个实例并保存在 ArmyList 中,客户需要时 可随机获取。
皇上:
import java.util.ArrayList;
import java.util.Random;
//单例模式的扩展
//单例模式可扩展为有限的多例(Multitcm)模式,这种模式可生成有限个实例并保存在 ArmyList 中,客户需要时 可随机获取。
public class Emperor {
//皇帝的姓名属性
private String name;
//定义最多能产生的实例的数量
private static final int maxNum=2;
//定义盛放所有皇帝实例的列表
private static ArrayList<Emperor> list=new ArrayList<Emperor>(2);
//产生所有的皇帝对象
static { for (int i=0;i<maxNum;i++){
list.add(new Emperor("皇帝"+i));
}
}
//构造方法私有,避免在类的外部创建对象
private Emperor() { }
private Emperor(String name) { this.name=name; }
//随机获取一个皇帝对象 点儿正
public static Emperor getInstance(){
int index=new Random().nextInt(maxNum);
return list.get(index);
}
//皇帝办公
public void work(){
System.out.println("我是"+name+",有事起奏,无事退朝!");
}
}
一如既往的大臣:
public class Minister {
public static void main(String[] args) {
//每天见面的都是同一个皇帝
for (int i=1;i<=5;i++){
Emperor emperor = Emperor.getInstance();
System.out.print("第"+i+"天:");
emperor.work();
}
}
}
运行结果:PS:每个人的运行结果都不一样,因为随机数的产生不一致
OK!可能还会补充。。待续