1,首先我们要知道单例模式的三大特点:
(1)单例类只能有一个实例。
(2)单例类必须自己创建自己的唯一实例。
(3)单例类必须给所有其他对象提供这一实例。
饿汉式:
首先先私有化构造函数
private HangryMan() {
System.out.println("哦豁,被调了");
}
接着创建自己的唯一实例 饿汉,饿汉,饿得要死 上来就开始吃(实例化一下自己这个类)
private static HangryMan hm = new HangryMan();
用static 表示保证了我们的实例唯一。
接着就是封装好get实例的方法,給外界调用
public static HungryMan getStance() {
return HungryMan ;
}
所有程序完毕,一个简单的饿汉式就完成了,下面看完整代码。
package com.hhq;
/**
* 单例模式中的饿汉式
*/
public class HungryMan {
/**
* 单例模式的特点私有化构造函数, 反正只要是单例 二话不说先私有化构造方法
* 但是饿汉式有缺点,因为它是直接new了一个这个对象,这个对象里存在的属性
* 都会在我们的内存中占有一定的空间,但是这些属性如果又没有被使用的话就
* 等于浪费了空间,所以 我们还有一种懒汉式的单例模式
*/
private HungryMan () {
System.out.println("哦豁,被调了");
}
//饿汉式 饿汉式 饿得要要死 上来先给你吃了(实例化一下自己这个类)
//保证了我们这个类都是指向于同一个对象
private static HungryMan s = new HungryMan ();
/**
* 用于取到sj对象
* @return
*/
public static HungryMan getStance() {
return HungryMan ;
}
public static void main(String[] args) {
HungryMan .getStance();
}
}
但是饿汉式有一个致命的缺点,就是它是在类加载的时候就会创建自己的唯一对象,假如这个对象里面还有很多的属性,那么这些属性都会在我们的内存中占一定的空间,但是我们又并没有实际的用到这些属性的话,就会造成资源浪费。所以这个时候就有了我们的懒汉式。
懒汉式
懒汉式是在这个类第一次被调用的时候new,懒汉嘛,意思就是比较懒的意思,需要别人的来调用它,但是这样子就节省了资源。
具体步骤和饿汉式出入差不多只是它实例化自己的代码放在了get方法中。
private static Lh lh;
private static Lh getStance() {
if(lh == null) {
lh = new Lh();
}
}
完整代码如下:
package com.hhq;
/**
* 懒汉式
* 它是在这个类第一次被调用时 new出来这个类 ,懒汉嘛 就
* 比较懒的意思,需要别人来调它 但是这样子可以给我们节省一定的资源
* 但是这里也是有bug的,单线程下是OK的,多线程会出问题
*/
public class Lh {
/**
* 先私有化构造函数
*/
private Lh() {
System.out.println(Thread.currentThread().getName() + "ok");
System.out.println("懒汉式。。。。");
}
private static Lh lh;
private static Lh getInstance() {
if(lh == null) {
lh = new Lh();
}
return lh;
}
public static void main(String[] args) {
Lh.getInStance();
}
但是这样子还是有问题存在的,这种在单线程的情况下是没有问题的,如果在多线程情况下呢?
我们会发现会有多个Lh对象被实例出来,这就打破了单例模式的原则了!当然解决办法也很简单,加锁。
private static Lh getInstance() {
//双重检测锁模式 下的 懒汉模式 俗称 DCL懒汉式
if( lh == null) {
//先上锁,一次只允许一个线程拿到
synchronized (Lh.class) {
if(lh == null){
lh = new Lh(); // 这不是一个原子性的操作
/**
* 1,分配内存空间
* 2,执行构造方法,初始化对象
* 3,把这个对象指向这个空间
*
* 执行顺序一般是 123
* 但是它也有可能会是132
*
* 当一个线程执行的顺序是132 时 另一个线程进来的时候回判断这个不是空的,而此时这个空间中是并没有
*/
}
}
}
这里我们采用的是双重检测锁模式,即DCL懒汉式。但是这里还是有一个问题,lh = new Lh()这并并不是一个原子性的操作。这里我们要知道什么是原子性咯:原子性即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
在实例化这个对象的时候,会经历以下三个过程:(1)分配内存空间 (2)执行构造方法,初始化对象,(3)把这个对象指向这个空间。但是它的执行顺序有可能是1 ->2->3 如果是1->3->2的话,那么此时这个对象是在我们内存中有一席之地的,但是它里面的东西就可能是空的。接着下一个线程进来的时候就会进行一次判断,判定它是存在,但是呢又是空。就造成了一个对象为空的现象。这时我们只需要在我们的方法中加上一个volatile 进行。
但是这些都是不安全的,我们的反射都可以破解,破坏这个单例模式。