设计模式之单例设计模式

单例模式,就是只有一个实例对象存在,保证了多线程情况下的线程安全问题。保证一个类仅有一个实例,并提供一个访问它的全局访问点。

创建单例对象的三要素:

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。

主要应用场景:

  1. Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  2. 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
  3. 要求生产唯一序列号。
  4. WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  5. 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

实现单列模式的关键私有构造方法

单例模式的七种实现方式:

单例模式主要分为饿汉式懒汉式两种
单列模式主要考虑

  • 实例对象唯一
  • 懒加载
  • 性能问题

1、饿汉式

这是饿汉式的简单实现保证了线程安全,但是浪费内存的开销,没有实现懒加载。

package com.multi.thread.two.design.pattern;

/**
 * 饿汉式:实现步骤
 * 1、私有构造方法
 * 2、新建实例对象
 * 3、对外提供获取对象的全局访问点
 * 保证线程安全,
 * 只是浪费内存,无法保证懒加载
 */
public class SingletonObject1 {
    private static final SingletonObject1 instance = new SingletonObject1();

    private SingletonObject1() {

    }

    public static SingletonObject1 getInstance() {
        return instance;
    }

}

2、懒汉式,线程不安全

这种方式实现了懒加载,多线程的情况,会有线程安全问题。

package com.multi.thread.two.design.pattern;

/**
 * 实现懒加载,但是在多线程的情况下,可能会有多份实例
 *
 */
public class SingletonObject2 {

    private static SingletonObject2 instance;

    private SingletonObject2() {
    }

    public static SingletonObject2 getInstance() {
        if (null == instance) {
            instance = new SingletonObject2();
        }
        return SingletonObject2.instance;
        //return instance;
    }
}

3、懒汉式,线程安全

synchronized关键字实现线程安全,但是性能不好,每次获取对象的时候都会加加锁。

package com.multi.thread.two.design.pattern;

/**
 * synchronized关键字实现线程安全
 * 性能不好,第二次获取对象,也会加锁
 *
 */
public class SingletonObject3 {

    private static SingletonObject3 instance;

    private SingletonObject3() {
    }
    // 加锁
    public synchronized static SingletonObject3 getInstance() {
        if (null == instance) {
            instance = new SingletonObject3();
        }
        return SingletonObject3.instance;
        //return instance;
    }
}

4、懒汉式,线程安全

double check的实现线程安全。加锁只在两个对象竞争对象锁的时候,第二次获取对象的时候,不会再加锁,提高了性能。但是这种可能会存在空指针的异常,
空指针的隐患:编译重排序。 java编译时的优化

  • 当第一次进入获取对象的时候,新建实例对象,在堆中开辟内存。
  • 当构造方法中还有初始化的东西,没有初始化完。就返回了对象的实例
  • 另外的线程进入获取对象,获取没有初始化完的对象,就会造成空指针的异常
package com.multi.thread.two.design.pattern;

/**
 * double check :加锁只是在创建对象的时候加锁一次
 *         以后获取的时候都是直接获取对象,提高了性能
 *
 *  单列,懒加载,提高性能
 *
 *  空指针的隐患:编译重排序。 java编译时的优化
 *  当第一次进入获取对象的时候,新建实例对象,在堆中开辟内存。
 *  当构造方法中还有初始化的东西,没有初始化完。就返回了对象的实例
 *  另外的线程进入获取对象,获取没有初始化完的对象,就会造成空指针的异常
 *
 */
public class SingletonObject4 {

    private static SingletonObject4 instance;

    private SingletonObject4() {
        // 构造函数初始化
    }
    // 加锁
    public static SingletonObject4 getInstance() {
        if (null == instance) {
            synchronized (SingletonObject4.class) {
                if (null == instance) {
                    instance = new SingletonObject4();
                }
            }
        }
        return SingletonObject4.instance;
        //return instance;
    }
}

5、懒汉式,线程安全

加volatile关键字实现了变量的可见性,有序性。避免了重排序的问题。即java虚拟机对我们的代码的优化,代码执行保证最终的一致性,可能造成代码的执行的顺序和理论上的不一致的问题。

package com.multi.thread.two.design.pattern;

/**
 * Created by jingxingqiang on 2020/1/20 21:15
 * <p>
 *     加volatile,不能保证原子性,但是保证内存的可见性。多个线程看到的数据是同一份
 *     内存的可见性
 *     可见性
 */
public class SingletonObject5 {

    private static volatile SingletonObject5 instance;

    private SingletonObject5() {
        // 构造函数初始化
    }

    // 加锁
    public static SingletonObject5 getInstance() {

        if (null == instance) {
            synchronized (SingletonObject5.class) {
                if (null == instance) {
                    instance = new SingletonObject5();
                }
            }
        }
        return SingletonObject5.instance;
        //return instance;
    }
}

6、静态内部类

无锁提高性能,对象在用的时候才去加载,实现懒加载。
充分利用了static变量只会被加载一次的特点。

package com.multi.thread.two.design.pattern;

/**
 * 没有加锁,提高性能
 * 懒加载
 * 单列
 */
public class SingletonObject6 {

    private SingletonObject6() {
    }

    private static class InstanceHolder {
        // static只会初始化一次
        private static final SingletonObject6 instance = new SingletonObject6();
    }

    public SingletonObject6 getInstance() {
        return InstanceHolder.instance;
    }
}

7、枚举的方式

  • 枚举的方式
  • 枚举类型线程安全,只会被装载一次,
  • 当调用枚举的类的时候,枚举初始化构造方法,就会创建我们需要的对象,
  • 枚举只是被加载一次,保证了线程的安全,实例只会存在一份
package com.multi.thread.two.design.pattern;

import java.util.stream.IntStream;

/**
 
 * 枚举的方式
 * 枚举类型线程安全,只会被装载一次,
 * 当调用枚举的类的时候,枚举初始化构造方法,就会创建我们需要的对象,
 * 枚举只是被加载一次,保证了线程的安全,实例只会存在一份
 */
public class SingletonObject7 {

    private SingletonObject7() {
    }

    // 枚举类型线程安全,只会被装载一次
    private enum Singleton {
        INSTANCE;

        private final SingletonObject7 instance;

        Singleton() {
            instance = new SingletonObject7();
        }

        public SingletonObject7 getInstance() {
            return instance;
        }
    }

    public static SingletonObject7 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    public static void main(String[] args) {
        IntStream.rangeClosed(1, 100).forEach(i -> new Thread(String.valueOf(i)) {
            @Override
            public void run() {
                System.out.println(SingletonObject7.getInstance());
            }
        }.start());
    }


}

以上就是对单列模式常用的七种方式的饿总结
项目中用的比较多的 是1,2,4,5,6的方式

推荐使用:1,4,6,7的方式。枚举在目前的使用中比较少,但是在github开源的项目中使用的比较多,也推荐使用。 其实在项目看到使用做多的是第三种的方式,这种方式在并发很大的情况可能会有线程安全的问题存在。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值