赞
踩
答案:
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡 的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个 Printer Spooler,以避免 两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。 总之,选择单例模式就是为了避免不一致状态。
答案:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- 单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。
答案:
- 构造私有;
- 以静态方法或者枚举返回实例 ;
- 确保实例只有一个,尤其是多线程环境 ;
- 确保反序列换时不会重新构建对象。
答案:
(1)饿汉式(立即加载);
(2)懒汉式(延迟加载);
(3)同步锁(解决线程安全问题):
(4)双重检查锁(提高同步锁的效率);
(5) 静态内部类;
(6)内部枚举类实现(防止反射攻击);
答案:
- Windows的Task Manager(任务管理器)就是很典型的单例模式
- windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
- 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
- Application 也是单例的典型应用(Servlet编程中会涉及到)
- 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
- 在servlet编程中,每个Servlet也是单例
- 在spring MVC框架/struts1框架中,控制器对象也是单例
- 一个产品注册了一个商标,那么它就是单例的
单例模式有两种类型:
- 懒汉式:在真正需要使用对象时才去创建该单例类对象
- 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动 时直接产生一个单例对象,然后永久驻留内存的方式来解决
- 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计 一个单例类,负责所有数据表的映射处理
先创建后使用,线程安全,占用内存。代码如下:
/**
* 饿汉式单例模式
*/
public class ClassA {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassA(){ }
//2.在类的内部创建一个类的实例
//类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
private static final ClassA instance = new ClassA();
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
//方法没有同步,调用效率高!
public static ClassA getInstance(){
return instance;
}
//测试
public static void main(String[] args) {
ClassA a = ClassA.getInstance();
ClassA b = ClassA.getInstance();
System.out.println(a==b);
}
}
控制台输出的结果如下图:
用的时候才创建,线程不安全,加锁会影响效率。资源利用率高了,但是,每次调用getInstance()方法都要同步,并发效率较低。代码如下:
/**
* 懒汉式单例模式
*/
public class ClassB {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassB(){ }
//2.在类的内部创建一个类的实例
private static ClassB instance ;
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static synchronized ClassB getInstance(){
if(instance == null) {
instance = new ClassB();
}
return instance;
}
//测试
public static void main(String[] args) {
ClassB a = ClassB.getInstance();
ClassB b = ClassB.getInstance();
System.out.println(a==b);
}
}
控制台输出的结果如下图:
也是饿汉式和懒汉式的组合,调用getInstance()方法时才创建,达到了类似懒汉式的效果,同时又是线程安全的。代码如下:
/**
* 使用静态内部类方式实现单例模式
*/
public class ClassC {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassC(){ }
//2.在类的内部创建一个类的实例
private static class Holder{
private static ClassC instance = new ClassC();
}
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassC getInstance(){
return Holder.instance;
}
//测试
public static void main(String[] args) {
ClassC a = ClassC.getInstance();
ClassC b = ClassC.getInstance();
System.out.println(a==b);
}
}
控制台输出的结果如下图:
线程安全,实现简单,调用效率高,不能延时加载。枚举本身就是单例模式,由JVM从根本上提供保障并且可以天然的防止反射和反序列化漏洞!需要继承的场景它就不适用了。枚举方式是Effective Java作者提倡的方式。代码如下:
/**
* 使用枚举方法实现单例模式
*/
public enum ClassD {
//定义一个枚举的元素,它就代表了Singleton的一个实例。
INSTANCE;
//对外部提供调用方法:将创建的对象返回,只能通过类来调用
public void otherMethod(){
//功能处理
}
//测试
public static void main(String[] args) {
ClassD a = ClassD.INSTANCE;
ClassD b = ClassD.INSTANCE;
System.out.println(a==b);
}
}
通常线程安全,加volatile的作用是禁止指令重排。(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)代码如下:
/**
* 使用双重校验锁来实现单例模式
*/
public class ClassE {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassE(){ }
//2.在类的内部创建一个类的实例
private volatile static ClassE instance; //volatile作用:保证多线程可以正确处理instance
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassE getInstance(){
if(instance == null){ //检查实例,如果为空,就进入同步代码块
synchronized (ClassE.class){
if(instance == null){ //再检查一次,仍未空才创建实例
instance = new ClassE();
}
}
}
return instance;
}
//测试
public static void main(String[] args) {
ClassE a = ClassE.getInstance();
ClassE b = ClassE.getInstance();
System.out.println(a==b);
}
}
控制台输出的结果如下图:
线程安全,ThreadLocal采用以空间换时间的方式,为每一个线程都提供一份变量,因此可以同时访问而互不影响。代码如下:
/**
* 使用ThreadLocal实现单例模式
*/
public class ClassF {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassF(){ }
//2.在类的内部创建一个类的实例
private static final ThreadLocal<ClassF> tls = new ThreadLocal<ClassF>(){
@Override
protected ClassF initialValue(){
return new ClassF();
}
};
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassF getInstance(){
return tls.get();
}
//测试
public static void main(String[] args) {
ClassF a = ClassF.getInstance();
ClassF b = ClassF.getInstance();
System.out.println(a==b);
}
}
控制台输出的结果如下图:
(CAS锁(Compare and Swap):比较并交换,是一种有名的无锁算法,属于乐观锁)。用CAS锁来实现单例模式是线程安全的,代码如下:
/**
* 使用CAS锁来实现单例模式
*/
public class ClassG {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassG(){ }
//2.在类的内部创建一个类的实例
private static final AtomicReference<ClassG> instance = new AtomicReference<ClassG>();
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static final ClassG getInstance(){
for(;;){
ClassG current = instance.get();
if(current != null){
return current;
}
current = new ClassG();
if(instance.compareAndSet(null,current)){
return current;
}
}
}
//测试
public static void main(String[] args) {
ClassG a = ClassG.getInstance();
ClassG b = ClassG.getInstance();
System.out.println(a==b);
}
}
控制台输出的结果如下图:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。