系列文章目录
前言
1、它是设计模式里面最简单的一种;
2、它是高频面试题,这也就是为什么单例模式要求必须自己能够完全手写出来的原因
3、说白了饿/恶汉式就是枚举,第1种是正常的枚举、第2种是旧版的枚举、第3种是旧版本的基础上方法私有化
一、单例模式是什么?
1、这里可以涉及到很久以前一档节目叫《非你莫属》里面有个程序员直接用脚把单例模式写出来了,所以后来这道题就直接火了
2、初学单例模式可以跟枚举联系起来,枚举是指某个类型的对象有且仅有固定的几个,如果固定一个,这就是单例了
上图,它只有一个对象,就可以理解为单例了
前面是单例最简单的一种,但是我们的单例不止这一种
我们分类一下
1、饿/恶汉式
不管我们使用者是否需要这个对象,它都一上来就给你创建好这个唯一的对象
(1)比如上面的枚举类型就是
(2)
a、构造器私有化
b、用一个全局的静态常量来保存这个唯一的实例对象
实际上也是枚举如下,1.5之前实现枚举也只能这样:
(3)形式三
a、构造器私有化
b、用一个私有的静态的常量,来保存这个唯一的实例对象
c、提供一个静态方法,来返回这个常量对象
通过方法去获取,而不是直接暴露
饿/恶汉式就这三种
下面再来说说
2、懒汉式
非常重要,能不创建就不创建,免得浪费内存
延迟创建对象。当使用者来或者这个对象,要用到对象时,我再创建。
(1)形式一:
见下面,考虑线程安全问题和性能问题
因为掉一次new一次肯定不是同一个对象了
所以最终是这样的,这个也是标准的单例模式了
在绝大多事情况下这个确实没有问题,但是……
存在一个线程安全问题,不知道大家有没有发现
我们重现一下错误,演示一下
这里休眠一下(自己try-catch一下)
匿名内部类,运用多态给它赋值
那怎么解决呢?
加锁给它锁上
把这个方法锁上,同一时间就只能进来一个线程]
这样就可以了
但是……
安全问题解决了,发烧友还是认为影响了性能,性能不够快
因为有可能我这个线程以及创建好了,后面的线程没有必要排队,你理一理,是不是这个道理,所以还可以再加判断
之前第一次第二次,前面两个线程有竞争关系,后面没必要每次都锁
安全没有问题,但是认为不是最优的
3、公认优化版
为什么不能用this?
静态方法里面能出现this吗? 不能
解决办法:使用当前类的Class对象
效率还不够高再加一个条件判断,双重保险
缺点,这种模式嵌套的层数较多,可能稍微有点模糊,我们再看一下形式二,看看是不是会简单一些
(2)形式二:内部类形式
内部类的初始化,必须你用到它的时候它才会初始化
这就是线程安全的,并且简洁的懒汉式
这样我们就讲了5种形式
- 1、饿/恶汉式
- 不管我们使用者是否需要这个对象,它都上来先给你创建好这个唯一的对象。
- (1)枚举类型
- (2)形式二
- ①构造器私有化
- ②用一个全局的静态的常量,来保存这个唯一的实例对象
- (3)形式三
- ①构造器私有化
- ②用一个私有的静态的常量,来保存这个唯一的实例对象
- ③提供一个静态方法,来返回这个常量对象
- 2、懒汉式
- 延迟创建对象。当使用者来或者这个对象,要用到对象时,我再创建。
- (1)形式一:
- 见下面,考虑线程安全问题和性能问题
- (2)形式二:内部类形式
二、代码
package com.atguigu.test17;
import org.junit.Test;
/*
* 单例设计模式:
*
* 单例:某个类只能有唯一的一个实例对象。
*
* 如何实现单例?
* 1、饿/恶汉式
* 不管我们使用者是否需要这个对象,它都上来先给你创建好这个唯一的对象。
* (1)枚举类型
* (2)形式二
* ①构造器私有化
* ②用一个全局的静态的常量,来保存这个唯一的实例对象
* (3)形式三
* ①构造器私有化
* ②用一个私有的静态的常量,来保存这个唯一的实例对象
* ③提供一个静态方法,来返回这个常量对象
*
* 2、懒汉式
* 延迟创建对象。当使用者来或者这个对象,要用到对象时,我再创建。
* (1)形式一:
* 见下面,考虑线程安全问题和性能问题
* (2)形式二:内部类形式
*
*/
public class Test17 {
@Test
public void test1(){
SingleEnum s1 = SingleEnum.INSTANCE;
SingleEnum s2 = SingleEnum.INSTANCE;
System.out.println(s1 == s2);
}
@Test
public void test2(){
// SingleEnum.test();//此时我并没有需要用到这个对象,但是它也创建出来了
}
@Test
public void test3(){
SingleClass s1 = SingleClass.INSTANCE;
SingleClass s2 = SingleClass.INSTANCE;
System.out.println(s1==s2);
}
@Test
public void test4(){
Single s1 = Single.getInstance();
Single s2 = Single.getInstance();
System.out.println(s1 == s2);
}
@Test
public void test5(){
LazyClass s1 = LazyClass.getInstance();
LazyClass s2 = LazyClass.getInstance();
System.out.println(s2 == s1);
}
LazyClass s1;
LazyClass s2;
@Test
public void test6(){
//匿名的内部类,继承Thread类
Thread t1 = new Thread(){
public void run(){
s1 = LazyClass.getInstance();
}
};
Thread t2 = new Thread(){
public void run(){
s2 = LazyClass.getInstance();
}
};
t1.start();
t2.start();
try {
//这里用join的目的是,为了两个子线程都执行完,再执行主线程的System.out.println(s1);
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}
}
enum SingleEnum{
INSTANCE;
// public static void test(){
// //..
// }
}
class SingleClass{
public static final SingleClass INSTANCE = new SingleClass();
private SingleClass(){
}
}
class Single{
private static final Single INSTANCE = new Single();
private Single(){
}
public static Single getInstance(){
return INSTANCE;
}
}
class LazyClass{
private static LazyClass instance;
private LazyClass(){
}
public static LazyClass getInstance(){
if(instance == null){//提高效率
synchronized(LazyClass.class){//当前类的Class对象
if(instance == null){//安全判断
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazyClass();
}
}
}
return instance;
}
//安全没问题,但是认为不是最优的
/* public synchronized static LazyClass getInstance(){
if(instance == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazyClass();
}
return instance;
}*/
//有安全问题
/* public static LazyClass getInstance(){
// return new LazyClass();//错误的
if(instance == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazyClass();
}
return instance;
}*/
}
class Lazy{
private Lazy(){
}
private static class Inner{
public static final Lazy INSTANCE = new Lazy();//在内部类中,创建外部类的唯一对象
}
public static Lazy getInstance(){
return Inner.INSTANCE;
}
}
总结
1、单例模式有很多种,如果面试的时候说让你写一个最简单的,可以参考本文章饿汉式第一种写法,就三四行代码
2、本篇文章懒汉式 形式一是最优化的单例模,本篇文章懒汉式 形式二是最简洁的单例模式