单例模式:在多线程环境下,对外存在一个对象,并且提供一个访问该实例的全局访问点。
说白了就是给类“计划生育”,对外只能有一个。
应用场景:
* 常见应用场景:
* 1.Windows的Task Manager(任务管理器)就是典型的单例模式:启动多次都是一个管理器。
* 2.Windows的回收站也是单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
* 3.项目中。读取配置文件的类,一般也只有一个对象,没有必要每次使用配置文件数据,每次new
* 一个对象去获取。
* 4.网站的计数器,一般也是采用单例模式实现,否则难以同步。
* 5.应用程序的日志应用,一般都用单例模式实现。由于共享的日志文件一直处于打开状态,因为只
* 能有一个实例去操作,否则内容不好追加。
* 6.数据库连续池的设计一般也是单例模式,因为数据库连续是一种数据库资源。
* 7.操作系统的文件系统,也是大的单例模式实现的例子。一个操作系统只能有一个文件系统。
* 8.Application也是单例(servlet编程中会涉及到)
* 9.在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
* 10.在service编程中,每个Service也是单例
* 11.在Spring MVC框架/struts1框架中,控制器对象也是单例
优点:
* 优点:由于单例模式只生成一个实例,减少了系统性能开销。当一个对象的产生需要比较多的
* 资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,
* 然后永久驻留内存的方式来解决。
* 单例模式可以在系统设置全局的访问点,优化共享资源访问,例如可以设计一个单例类,复责
* 所有数据表的映射处理
5种实现方式:
* 常见五种单例模式实现方式:
* 主要:
* 饿汉式:线程安全,调用效率高,不能延时家族。
* 懒汉式:线程安全,调用效率不高,可以延时加载。
* 其他:
* 双重检测锁式:由于JVM底层内部模型原因,偶尔会有问题,不建议使用。
* 静态内部类式:线程安全,调用效率高,可以延时加载。
* 枚举单例:线程安全,调用效率高,不能延时加载。
*步骤
* 1.构造器私有化,避免外部new构造器
* 2.提供私有的静态属性,存储对象的地址
* 3.提供公共的静态方法,获取属性
第一种饿汉式:
public class TestDcl {
/*
2. 饿汉式:类初始化时,立即加载这个对象,不能延时
*/
private static TestDcl instance =new TestDcl();
//1.构造器私有化
private TestDcl(){
}
//3.饿汉式:线程安全,不需要同步,效率高
public static TestDcl getInstance(){
return instance;
}
}
第二种懒汉式
public class TestDcl {
/*
2.提供私有的静态属性
没有volatile其他线程可能访问一个没有初始化的对象,
因为新建一个对象是要开辟空间,然后初始化对象,最后返回对象的地址给引用,
如果创建一个对象比较慢,那么会先一步返回地址,还没初始化,那么得到的是一个空的对象。
volatile保证线程间变量的可见性,也就是变量修改后会马上写到主内存里,然后从主内存拿
最新的,而不是缓存。
懒汉式:延时加载,用的时候才加载,不会造成浪费,资源利用率高。
*/
private static volatile TestDcl instance;
//1.构造器私有化
private TestDcl(){
}
//3.需要加同步,调用效率低。
public static synchronized TestDcl getInstance(){
if (null == instance) {
instance = new TestDcl();
}
return instance;
}
}
第三种双重检测锁式:
public class TestDcl {
/*
2.双重检测锁式:结合了饿汉懒汉的优点:提高了执行的效率,
不必每次获取对象时都进行同步,只有第一次才同步,创建了
以后就没必要了。
但由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题,不推荐使用。
*/
private static TestDcl instance =null;
//1.构造器私有化
private TestDcl(){
}
//3.
public static TestDcl getInstance(){
if (null == instance) {
TestDcl dcl;
synchronized (TestDcl.class) {
dcl =instance;
if(dcl ==null){
synchronized (TestDcl.class){
if(dcl ==null){
dcl =new TestDcl();
}
}
instance = dcl;
}
}
}
return instance;
}
}
第四种静态内部类式:
public class TestDcl {
/*
2. 静态内部类式:也是一种懒加载方式
*/
private static class DclClassInstance{
private static final TestDcl instance = new TestDcl();
}
//1.构造器私有化
private TestDcl(){
}
//3.
public static TestDcl getInstance(){
return DclClassInstance.instance;
}
}
第五种枚举:
enum Dcl{
//这个枚举元素本身就是单例,由JVM从根本上提供保障,避免通过反射和反序列的漏洞,枚举是最安全的。
INSTANCE;
//添加自己需要的操作
}
调用:可以每个模式都试下,对象是一样的。
public static void main(String[] args) {
Thread thread = new Thread(()->{
System.out.println(TestDcl.getInstance());
});
thread.start();
System.out.println(TestDcl.getInstance());
System.out.println(Dcl.INSTANCE ==Dcl.INSTANCE);
}
结果:
cn.oyh.thread.TestDcl@6d311334
cn.oyh.thread.TestDcl@6d311334
true
怎么选择用哪个模式?
*如何选用?
* 占用资源少 ,不需要延时加载:枚举式 好于 饿汉式
* 占用资源大,需要延时加载:静态内部类 好于 懒汉式