相信大家面试的时候,经常会被问到,23种设计模式,你熟悉哪写,手写个单例啥的,当然了,这是对于小程序员的面试,大佬请忽略
单例模式呢,顾名思义就是这个对象只会创建一次。因此他的构造方法必须私有。
那么这只创建一次的对象等你用的时候,又怎么用呢?肯定是需要一个方法返回提供同一个对象,也就是唯一的对象。
单例模式
单例模式(Singleton Pattern): 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
单例模式有三个要点:
- 构造方法私有化;
- 实例化的变量引用私有化;
- 获取实例的方法共有
因此代码我们可以这样写(通过学习观看,总共7种写法):
1、饿汉式
package com.app.singlecase;
/**
* @ClassName: HungryMan
* @Description 单例模式饿汉式
* @Date:2019/9/29 11:05
* @Author: lk
* @see
* @since JDK 1.8
*/
public class HungryMan {
//一个对象
private final static HungryMan HUNGRY_MAN=new HungryMan();
//私有构建公开方法
private HungryMan(){
}
//公开方法 获取这个对象
public static HungryMan getHungryMan(){
return HUNGRY_MAN;
}
}
优点: 简单,使用时没有延迟;在类装载时就完成实例化,天生的线程安全
缺点: 没有懒加载,启动较慢;如果从始至终都没使用过这个实例,则会造成内存的浪费。
2、饿汉式的变种写发
package com.app.singlecase;
/**
* @ClassName: HungryManTwo
* @Description 单例模式饿汉式的变种
* @Date:2019/9/29 11:10
* @Author: lk
* @see
* @since JDK 1.8
*/
public class HungryManTwo {
private HungryManTwo(){
}
private static final HungryManTwo HUNGRY_MAN_TWO;
static {
HUNGRY_MAN_TWO=new HungryManTwo();
}
private static HungryManTwo getHungryManTwo(){
return HUNGRY_MAN_TWO;
}
}
优缺点同上
3、懒汉式(线程不安全)
package com.app.singlecase;
/**
* @ClassName: Slacker
* @Description 单例模式懒汉式
* @Date:2019/9/29 11:13
* @Author: lk
* @see
* @since JDK 1.8
*/
public class Slacker {
private Slacker(){
}
private static Slacker slacker;
public static Slacker getSlacker(){
//slacker==null存在竞态条件,可能会有多个线程同时进入if语句,产生多个实例
if(slacker==null){
System.out.println("第一次进入");
slacker=new Slacker();
}
return slacker;
}
}
优点: 懒加载,启动速度快、如果从始至终都没使用过这个实例,则不会初始化该实力,可节约资源
缺点: 多线程环境下线程不安全。if (singleton == null) 存在竞态条件,可能会有多个线程同时进入 if 语句,导致产生多个实例
4、懒汉式的变种(线程安全)
package com.app.singlecase;
/**
* @ClassName: SlackerTwo
* @Description 单例模式懒汉式线程安全
* @Date:2019/9/29 11:18
* @Author: lk
* @see
* @since JDK 1.8
*/
public class SlackerTwo {
private SlackerTwo(){
}
private static SlackerTwo slackerTwo;
public static synchronized SlackerTwo getSlackerTwo(){
if(slackerTwo==null){
slackerTwo=new SlackerTwo();
}
return slackerTwo;
}
}
优点: 解决了上述的安全问题
缺点: 使用synchronized
关键字对方法进行加锁,使得每次只有一个线程进入该方法,在多线程下,效率降低。
5、双重死锁
package com.app.singlecase;
/**
* @ClassName: DoubleCheckLock
* @Description 单例模式之双重检查锁
* @Date:2019/9/29 11:24
* @Author: lk
* @see
* @since JDK 1.8
*/
public class DoubleCheckLock {
private DoubleCheckLock(){
}
private static volatile DoubleCheckLock doubleCheckLock;
public static DoubleCheckLock getDoubleCheckLock(){
if(doubleCheckLock==null){
synchronized (DoubleCheckLock.class){
if(doubleCheckLock==null){
doubleCheckLock=new DoubleCheckLock();
}
}
}
return doubleCheckLock;
}
}
优点:线程安全;延迟加载;效率较高。
由于 JVM 具有指令重排的特性,在多线程环境下可能出现 singleton 已经赋值但还没初始化的情况,导致一个线程获得还没有初始化的实例。volatile 关键字的作用:
- 保证了不同线程对这个变量进行操作时的可见性
- 禁止进行指令重排序
6、静态内部类
package com.app.singlecase;
/**
* @ClassName: Inside
* @Description 单例模式之静态内部类
* @Date:2019/9/29 11:29
* @Author: lk
* @see
* @since JDK 1.8
*/
public class Inside {
private Inside(){
}
//静态内部类
private static class SingletonInstance {
private static final Inside INSIDE=new Inside();
}
public static Inside getInside(){
return SingletonInstance.INSIDE;
}
}
优点: 避免了线程不安全,延迟加载,效率高。
静态内部类的方式利用了类装载机制来保证线程安全,只有在第一次调用getInside方法时,才会装载SingletonInstance内部类,完成Singleton的实例化,所以也有懒加载的效果。
7、枚举(最简单且线程安全外加能防止序列号攻击)
package com.app.singlecase;
/**
* @ClassName: SingletonEnum
* @Description 单例模式之枚举模式
* @Date:2019/9/29 11:32
* @Author: lk
* @see
* @since JDK 1.8
*/
public enum SingletonEnum {
SINGLETON_ENUM;
public void getEnumTest(){
System.out.println("枚举测试");
}
}
优点: 通过JDK1.5中添加的枚举来实现单例模式,写法简单,且不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
作者也是看的另外一片博客学习的,下面是那个博客的链接
作者学习的博客地址
祝大家面试顺利,一起加油。