JAVA设计模式——单例模式

设计模式

单例模式

介绍

意图: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决: 一个全局使用的类频繁地创建与销毁。

优点:

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 2、避免对资源的多重占用(比如写文件操作)。

缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景:

  • 1、要求生产唯一序列号。
  • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项: getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

简单实现:

单线程下最简单的单例模式:

public class LazySingletonTest {
    public static void main(String[] args) {
        LazySingleton instance = LazySingleton.getInstance();
        LazySingleton instance1 = LazySingleton.getInstance();
        System.out.println(instance == instance1);
        //输出:true
    }

}


class LazySingleton{
    private static LazySingleton instance;
    private LazySingleton(){

    }
    public static LazySingleton getInstance(){
        if (instance == null){
            instance = new LazySingleton();
        }
        return  instance;
    }
    
    

当多线程同时走到这段代码的时候,就会产生两个实例

public class LazySingletonTest {
    public static void main(String[] args) {

        new Thread( ()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();

        new Thread( ()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();

		//输出:
		//com.sms.designpattern.LazySingleton@30f3ad2a
		//com.sms.designpattern.LazySingleton@697d5cae
    }
}

class LazySingleton{
    private static LazySingleton instance;
    private LazySingleton(){

    }
    public static LazySingleton getInstance(){
        if (instance == null){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazySingleton();
        }
        return  instance;
    }

}

多线程下 实例方法新增synchronized关键字即可解决

public class LazySingletonTest {
    public static void main(String[] args) {

        new Thread( ()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();

        new Thread( ()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();
        //输出:
		//com.sms.designpattern.LazySingleton@697d5cae
		//com.sms.designpattern.LazySingleton@697d5cae


    }
}

class LazySingleton{
    private static LazySingleton instance;
    private LazySingleton(){

    }
    public synchronized static LazySingleton getInstance(){
        if (instance == null){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazySingleton();
        }
        return  instance;
    }

}

也就是说,这样子的话每次多线程运行都会进行一个加锁的操作,这样子能解决多实例的问题,但是这样做会损耗性能

我们加锁的目的是想要只有一个实例,把锁加在方法上,不管后面有没有走到加载实例的时候都会进行加锁,没有必要

懒加载最终单例模式:

public class LazySingletonTest {
    public static void main(String[] args) {

        new Thread( ()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();

        new Thread( ()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();


    }
}

class LazySingleton{
    private volatile static LazySingleton instance; //volatile 禁止指令重排
    private LazySingleton(){

    }
    public  static LazySingleton getInstance(){
        if (instance == null){
            //当两个方法走到这里,同样会实例化两次,所以要再进行一次判断
            //在这里多并发的情况还是会有性能损耗
            synchronized (LazySingleton.class){
                if (instance == null){
                    instance = new LazySingleton();
                    //字节码层 javap反编译
                    // JIT ,CPU 可能会进行指令重排 (第二步、第三步会乱序)
                    //1. 分配空间
                    //2. 初始化
                    //3. 引用赋值

                }
            }

        }
        return  instance;
    }

}

饿汉模式简单单例模式

public class HungrySingletonTest {
    public static void main(String[] args) {

        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton instance1 = HungrySingleton.getInstance();
        System.out.println(instance == instance1);
    }

}

// 饿汉模式
class HungrySingleton{
    private static  HungrySingleton instance = new HungrySingleton();
    private HungrySingleton(){

    }
    public static HungrySingleton getInstance(){
        return instance;
    }


}

基础JVM的类加载机制来保证线程安全

饿汉模式:

类加载的初始化阶段就完成了 实例的初始化。本质上就是借助于JVM类加载机制,保证实例的唯一性。

类加载过程:

  1. 加载二进制数据到内存中,生成对应的class数据结构
  2. 连接:a. 验证 b.准备(给类的静态成员变量赋默认值) c.解析
  3. 初始化:给类的静态变量赋初值

只有在真正使用对应的类时,才会触发初始化 如(当前类是启动类即main函数所在类,直接进行new操作,访问静态属性、访问静态方法,用反射访问类,初始化一个类的子类等)

静态内部类实现

public class InnerClassSingletonTest {
    public static void main(String[] args) {
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
        System.out.println(instance==instance1);
    }
}
//静态内部类实现
//本质上也是jvm类加载机制实现线程安全
//懒加载,不主动调用也不会加载实例
class InnerClassSingleton{
    private static  class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){

    }
    //调用到此方法才会加载实例
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用)

1 public enum SingletonDemo4 {
2      
3     //枚举元素本身就是单例
4     INSTANCE;
5      
6     //添加自己需要的操作
7     public void singletonOperation(){     
8     }
9 }

反射修改多例

反射攻击可以对懒汉模式进行多实例且无法阻挡,饿汉模式和静态内部类可以防护

	private final static HungrySingleton hungrySingleton = new HungrySingleton();
	private HungrySingleton(){
        // 【饿汉式】防止反射攻击
        if(hungrySingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
java设计模式大体上分为三大类: 创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 设计模式遵循的原则有6个: 1、开闭原则(Open Close Principle)   对扩展开放,对修改关闭。 2、里氏代换原则(Liskov Substitution Principle)   只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。 3、依赖倒转原则(Dependence Inversion Principle)   这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。 4、接口隔离原则(Interface Segregation Principle)   使用多个隔离的借口来降低耦合度。 5、迪米特法则(最少知道原则)(Demeter Principle)   一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 6、合成复用原则(Composite Reuse Principle)   原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值