青岛seo整站优化公司,wordpress创建自定义分类法,宁波专业定制网站建设,做旅游网站平台合作入驻#x1f389;#x1f389;#x1f389;点进来你就是我的人了 博主主页#xff1a;#x1f648;#x1f648;#x1f648;戳一戳,欢迎大佬指点!人生格言#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔#x1f9be;点进来你就是我的人了 博主主页戳一戳,欢迎大佬指点!人生格言当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔 目标梦想进大厂立志成为一个牛掰的Java程序猿虽然现在还是一个嘿嘿谢谢你这么帅气美丽还给我点赞比个心 目录 一. 什么是单例模式
二. 单例模式的类型
三. 单例模式优缺点
四. 创建单例模式
1.饿汉式实现单例模式
2. 懒汉式实现单例模式
3. 两种实现方式的比较
五.解决懒汉单例的实现带来的线程不安全问题 一. 什么是单例模式
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时为了防止频繁地创建对象使得内存飙升单例模式可以让程序仅在内存中创建一个对象让所有需要调用的地方都共享这一单例对象。
二. 单例模式的类型
单例模式有两种类型
懒汉式在真正需要使用对象时才去创建该单例类对象饿汉式在类加载时已经创建好该单例对象等待被程序使用
三. 单例模式优缺点
优点 1、在内存里只有一个实例减少了内存的开销尤其是频繁的创建和销毁实例比如管理学院首页页面缓存。
2、避免对资源的多重占用比如写文件操作。
缺点没有接口不能继承与单一职责原则冲突一个类应该只关心内部逻辑而不关心外面怎么样来实例化。
主要解决一个全局使用的类频繁地创建与销毁。
何时使用当您想控制实例数目节省系统资源的时候。
如何解决判断系统是否已经有这个单例如果有则返回如果没有则创建。
关键代码构造函数是私有的。
注意
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
四. 创建单例模式
1.饿汉式实现单例模式
主要实现步骤:(饿汉主要体现在直接创建对象 1.先将构造方法私有化 避免用户直接在外部 new 新对象。 2.在类的内部属性部分直接创建一个对象我们已经将构造方法私有化了因此在外部没有办法 new 新对象所以只能在类的内部创建对象。 3.向外提供一个公共的静态方法用于外部引用该对象。创建好了对象后如果对外不提供公共方法我们就没有办法使用这个被创建的对象。 注意:之所以用static是因为类外无法直接创建对象,我们加上static就可以通过类名获取该类的属性和方法了. 代码演示:
public class Singleton {// 提供一个对象private static Singleton instance new Singleton();// 防止外部new对象private Singleton(){}// 使得外部能够访问到创建的对象public static Singleton getInstance(){return instance;}
}
2. 懒汉式实现单例模式
主要实现步骤: 1.先将构造器私有化 避免用户直接在外部 new 新对象。 2.在类的内部 声明一个对象引用但是不创建 即先不 new 3.向外提供一个公共的静态方法用于创建对象并返回对象引用。 (懒汉主要体现在不直接创建对象而是在需要的时候再创建) 代码演示:
public class Singleton {// 提供一个对象的引用private static Singleton instance null;// 防止外部new对象private Singleton(){}// 用于创建对象或使得外部能够访问到创建的对象public static Singleton getInstance(){if (instance null){instance new Singleton();}return instance;}
}
3. 两种实现方式的比较
两种实现方法都是通过将构造方法限定为private避免了类在外部被实例化在同一个虚拟机范围内Singleton的唯一实例只能通过getInstance()方法访问。
事实上通过Java反射机制是能够实例化构造方法为private的类的那基本上会使所有的Java单例实现失效。此问题在此处不做讨论姑且掩耳盗铃地认为反射机制不存在。
但是以上懒汉式单例的实现没有考虑线程安全问题它是线程不安全的并发环境下很可能出现多个Singleton实例相比较饿汉式单例的实现仅仅只是读取了变量的内容因此是线程安全的
五.解决懒汉单例的实现带来的线程不安全问题 在懒汉模式的这段代码中,既包含了读又包含了写且读写还不是原子的分成了两个步骤~ 所以存在线程安全问题。 举例解释说明: 比如两个线程t1t2同时进入该方法需要返回给 t1 t2 对象实例。当 t1 进入该方法后进行判断不为 null因此需要创建一个对象而在创建对象的过程中t2 刚好也进入了该方法也被判断成了不为 null此时t1, t2就指向不同的对象了所以它是线程不安全的。 对于原子性所引起的线程安全问题,前面我们有讲线程安全及解决方案(看这一篇就够了)
这里我们可以对这段代码直接加锁,这样就可以保证在进行读和写操作的时候只有一个线程在操作也就可以避免另一个线程同时进入 if 语句去创建对象了 public static Singleton getInstance(){//对类对象加锁synchronized (Singleton.class) {if (instance null) {instance new Singleton();}return instance;}}
问题解决了,可是这种方案真的好吗 对于加锁操作来说加锁确实可以让代码保证线程安全但是也会付出相应的代价 最典型的代价就是程序运行变慢了。本来我多个线程运行的好好的你给我加了把锁其他线程不就进不去了吗一定程度上的阻塞 此时我们可以再加一个判断判断 instance 是否被初始化完成如果已经初始化就不必进入加锁的代码块了
// 用于创建对象或使得外部能够访问到创建的对象public static Singleton getInstance(){//用于判断是否加锁if(instance null) {synchronized (Singleton.class) {//用于判断是否创建对象实例if (instance null) {instance new Singleton();}}}return instance;}
现在代码似乎看起来已经没有问题了,但实际上还有可能发生内存可见性问题! 如果多个线程都去调用 getInstance方法就会造成大量的读 instance 操作。而我们知道对于在内存读和在寄存器读来说后者的效率更高。因此编译器会自作主张对代码进行优化将读内存操作优化成读寄存器操作。 也就是说即使某个线程将instance初始化了但是由于编译器的优化在其他线程的视角看起来instance 还是null。 也就是说这里的内存可见性问题会导致第一个if语句失效即不该加锁的时候还会加锁。因此我们需要保证内存可见即使用 volatile 对 instance 进行修饰,被volatile修饰的变量都会从主内存中读取该变量。同时使用 volatile 可以防止 指令重排序。 至此,懒汉单例的实现带来的线程不安全问题基本得以解决!
对于线程安全问题不是太了解的可以参考我上一篇博客
线程安全及解决方案(看这一篇就够了)