欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java基础_设计模式_单例模式

程序员文章站 2022-03-07 17:05:30
...

刚刚接触单例模式的我们,在阅读大神写的代码的时候,有时很想不通为什么这里要使用单例这种模式,有啥好处吗,不这样写又会咋滴等问题的困扰。下面我就想比较通俗的、用自己的语言组织讲解一下单例模式,要是有地方理解不到位或出现偏差,希望大家能及时指出。


1.什么是单例模式?


2.为什么会有这种需求,在哪些地方用单例模式,原因或者好处是什么?


3.如何创建单例模式?常见的创建方式优缺点。


4.单列和工具类很像,比如math类是这样使用的Math.double(2,8);有啥区别呢?


1.单例模式:

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

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。


简单的讲就是:整个项目,有且只能创建唯一一个这个类的实例。 


2.为啥会有这样的需求呢?举个例子在电脑任务栏右键然后点击启动任务管理器,会弹出一个任务管理器窗口,然后你再重复这样的操作,看能再弹出一个任务管理器的窗口吗?肯定不能!要是谁能,请私信我。哈哈哈。。。这里对弹出窗口唯一限制用的就是单列模式。有且只能创建唯一一个实例即确保对象的唯一性。

为了确保对象的唯一性我们可以使用单列,但是为什么要确保对象的唯一性?

我认为好处有两点:

①省资源。项目中大量使用一个类,比如网络请求框架,要是每次使用创建对象都new的话,很浪费资源的。

比如我封装的网络框架使用时是这样的:ApiService.getInstance().getData().enqueue(//请求回调操作);

②就像弹出任务管理器一样要统一,只能有一个。


3.创建单列的方式:

最简单的就是根据单列的概念去创建。即有且只有一个对象。①私有的构造方法,不允许外部代码通过new的方式创建对象。②获取对象的方法中加上是否已经存在的判断,有则直接返回,没有则new一个。

如下:

/**
 * 单利模式总结
 * 简单的讲单列要达到什么效果?整个项目中 有且只能产生一个类的实例
 * 我认为使用单列模式原因主要有以下两点
 * 1.减少内存的占用 要是在每一次使用的时候都new一个对象,相当占用资源
 * 2.有些情况适合使用单列 比如桌面上弹出任务管理器窗口 是只能弹出一个的
 * 3.如何使用单列?
 */

public final class Singleton {

    private static Singleton singleton;

    //1.私有的构造 要是修饰public或者不写的话 类创建对象的时候直接走默认的构造 private意味着对于外部代码而言不能通过new来实例化 但是类内部可以的
    private Singleton() {

    }

    //2.通过Singleton.getInstance() 获取唯一实例 加上判断 存在则返回不存在则直接本类中new 这是最简单的创建单例方式
    public static Singleton getInstance() {

        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

}

测试类:

/**
 * 测试单例
 */

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

        //        Singleton singleton = new Singleton();  提示错误:私有的构造不允许new
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

        if (instance1 == instance2) {
            System.out.println("说明是单例模式,只能创建一个实例");
        }
    }
}
说明创建了唯一的一个实例。

但是存在一个问题就是,当有多个线程并行调用getInstance()的时候,就会创建多个单列。也就是说在多线程下不能正常工作。最直接的方法就是加锁,只能一个一个的来,代码如下:

  public static Singleton getInstance() {.... }  改为  public synchronizeed  static Singleton getInstance() {...}

这样保证了同一时间只能有一个线程调用getInstance()方法。但是效率极低,很少有情况会使用同步啊。(关于同步和异步的概念,在文章末尾有简单的解释)

为了保证多线程访问我们一般会双重加锁:

双重检查锁:double-checked-locking   改变了3,4,5行代码

public final class Singleton {

    private static Singleton singleton;

    //1.私有的构造  要是修饰public或者不写的话 类创建对象的时候直接走默认的构造 private意味着对于外部代码而言不能通过new来实例化 但是类内部可以的
    private Singleton() {

    }

    //2.通过Singleton.getInstance() 获取唯一实例 加上判断 存在则返回不存在则直接本类中new  这是最简单的创建单例方式
    public synchronized static Singleton getInstance() {

        if (singleton == null) {                    //3.多个线程一起进入同步代码块的if
            synchronized (Singleton.class) {        //4.修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
                if (singleton == null) {            //5.保证进入一个 判断是不是有了 有则返回
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}

这样就解决了多线程访问单列的问题。


4.对比工具类:

给大家放一个常用的Math类的源代码:

以下是Math类:
public final class Math {

    /**
     * Don't let anyone instantiate this class.
     */
    private Math() {}

    public static native double sin(double a);

}
简单分析:

①私有的构造不允许外部代码以new的方式创建对象

②声明为final关键字不允许被其他类继承

③提供native修饰的静态方法,方法本身没有实现,意味着这样的计算是使用jni的方式,预计c的计算效率高。
     注:Native Method 就是一个java调用非java代码的接口。该方法没有具体的java语言实现,比如又可能是c。

④自己书写工具类的注意final关键字等这样的细节,尽量规范。

⑤哇塞,突然感觉好心酸啊。工具类竟然是一个不能new对象、不能有子类(final),得多么的寂寞。


以上就是自己对单例模式的理解,有问题请大家积极指出。


PS:线程同步和异步的概念

什么时候必须同步?什么叫同步?如何同步?

只要在几个线程之间共享变量,就必须使用synchronized同步(或者volatite易变的)确保一个线程可以看见另一个线程做的更改。一个线程所做的变化何时以及如何变成对其他线程可见。

同步:共享的资源在同一时刻只能被一个线程使用,这种方式成为同步。

为了防止多个线程并发对同一数据的修改,所以需要同步,否则会造成数据不一致,也就是所谓的线程安全。

同步:提交请求=》等待服务器处理=》处理完返回  这个期间客户端浏览器不能干任何事
异步:请求通过事件触发=》服务器处理(此时浏览器依然可以做其他事情)=》处理完毕。

简单的讲同步即有顺序
异步:大家一起上公家车,没有秩序,可以同时发生。

很多时候我们使用的是异步多线程来处理同一业务里的大量数据,好比一万个订单要处理,如果你使用一个线程顺序执行,一个个处理,非常耗时间;但是多线程就可以开100个线程异步处理,这样效率提高很多。