java基础_设计模式_单例模式
刚刚接触单例模式的我们,在阅读大神写的代码的时候,有时很想不通为什么这里要使用单例这种模式,有啥好处吗,不这样写又会咋滴等问题的困扰。下面我就想比较通俗的、用自己的语言组织讲解一下单例模式,要是有地方理解不到位或出现偏差,希望大家能及时指出。
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个线程异步处理,这样效率提高很多。
上一篇: 程序员必备网站