1、概念
java一种常用它设计模式,在这个设计模式中,单列对象的类保证存在一个实例,该类自己创建自己对象,并保证对象唯一,并对外提供一个返回该对象的方法。可以直接访问不需要再实例化。
2 意义
为什么需要单列模式?
如果一个全局类需要被频繁使用,过度创建和销毁对象将大大降低系统性能,单例模式在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例、避免对资源的多重占用(比如写文件操作)。
*列如一个班只有一个班长;
*多进程多线程的系统,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
总之:单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。
三要素:
构造方法私有化;
一个指向自己私有的静态对象引用;
一个返回自己实例的静态公共方法;
3 立即加载和延迟加载
-
立即加载 : 在类加载初始化的时候就主动创建实例;如饿汉式
-
延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。 代表懒汉式
4单线程下的单例模式
1饿汉式
public class Single {
private Single(){
}
private static Single s=new Single();
public static Single get(){
return s;
}
}
在这种方法下,由于在加载该类时就会对该类进行实例化,并且类只会加载一次,所以在调用该返回该对象方法,实例已经被创建,所以该方法可以保证实例绝对单一,但是在一定程度下造成了内存资源的浪费。
2 懒汉式
private Single(){
}
private static Single s;
public static Single get(){
if (s==null){
s=new Single();
}
return s;
}
在类被加载时并不会创建自己的实例,只有调用创建实例方法是在会创建,使用这种延迟加载在一定程度上减少内存资源浪费。
总之,从速度和反应时间角度来讲,饿汉式(又称立即加载)要好一些;从资源利用效率上说,懒汉式(又称延迟加载)要好一些
5多线程下的单列模式
作为两种经典的单例创建模式,饿汉式,懒汉式在单线程中都可以保证对象的单一,但是在多线程中已经存在出一些线程安全问题。
在多线程中,饿汉式依然能够保证实例单一,在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。
但是在懒汉式中,如果多个线程同时进入到 if (s==null){s=new Single();}代码块中,就有可能创建出多个实例。
1同步延迟加载---使用synchronized
public class Single {
private Single(){
}
private static Single s;
public static synchronized Single get(){
if (s==null){
s=new Single();
}
return s;
}
}
如果返回该对象的方法使用synchronized修饰,可以保证单例,使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
那什么是同步?
* 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
* 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
但是在加锁在使用上会影响性能。
2.双重校验锁
public class Single {
private Single(){
}
private static volatile Single s;
public static Single get(){
if (s==null) {
synchronized (Single.class) {
if (s == null) {
s = new Single();
}
}
}
return s;
}
}
在这种方法中,不但能够保证单一实例,而且提高性能,但是实现难度较为复杂。
使用双重判断,第一次调用该方法是,如果为空,进入同步代码块,如果非空,直接跳过,有效的避开了过多的同步,第一次创建实例时才使用到了同步。
但是在使用该方法时必须使用 volatile 修饰实例应用。
volatile作用:
首先需要知道jvm在使用new 实例化一个对象的基本步骤:
1:分配对象的内存空间
2:初始化对象
3:使对象引用指向刚分配的内存地址
但是这只是一种情况,在实际情况中,指令可能是无序的,(实现指令重排序)有可能执行顺序为1 3 2,由于在使用new 实例化一个对象是一个非原子性的操作,所以可能发生以下情况:
线程1执行到了第9行
使对象引用指向刚分配的内存地址,但是未初始化对象, 被线程 2 预占; 线程 2 检查实例是否为 null。因为实例不为 null,线程 2 得到一个不完整(未初始化)的 Singleton 对象;
如果使用volatile 修饰,能够防止指令的重排序。避免得到一个不完整(未初始化)的 Singleton 对象
3静态内部类
public class Single {
private Single(){
}
private static class Sin{
private static Single s=new Single();
}
public static Single get(){
return Sin.s;
}
}
该方法达到了双重校验锁一样的效率,实现更为简单,但是这种方法只适应于静态域的情况。
对比饿汉式,该方法同样保证了单例,但是在加载该类时,不一定实例化对象,只有通过显式调用 get方法时,才会显式装载 Sin类,从而实例化.
4 枚举
这是我新了解到的一种方法,于 JDK1.5 之后才加入 enum 特性,这种实现方式还没有被广泛采用,这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它更简洁,自动支持序列化机制,能避免多线程同步问题,绝对防止多次实例化。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
注意点!!
在使用单例模式时,我们必须使用单例类提供的公有工厂方法得到单例对象,而不应该使用反射来创建,否则将会实例化一个新对象。此外,在多线程环境下使用单例模式时,应特别注意线程安全问题
小结:
以上就是我对单例模式概述一点心得,以及了解到的6种经典实现单例方法。当然现实中还有其他方法,但是这集中比较经典。文章如有错误,欢迎批评指点。
本文深入解析单例模式的概念、意义及六种经典实现方法,包括饿汉式、懒汉式、同步延迟加载、双重校验锁、静态内部类及枚举,探讨其在多线程环境下的线程安全问题。
564

被折叠的 条评论
为什么被折叠?



