设计模式(一)单例模式之详解
如果哪位大神觉得文章编写不够详细的,可以通过扫描下方二维码进行讨论--一起进步
一、单例模式概念
1.1、什么是单例模式?
答:确保一个类在应用中只有一个实例。
1.2、你有使用过单例模式吗?
答:
JDK:JDK中Runtime类就是使用单例。
开发中:系统资源文件(文件路径、数据库连接、系统常量配置)
1.3、单例模式的优点?
答:1、内存中只有一个对象,节省空间。
2、可以避免频繁的创建和销毁对象。
3、避免共享资源的多重占用。
1.4、单例模式与静态类的区别?
答:1、单例可以继承类、实现接口,静态类不行。
2、单例模式可以延迟加载,静态类一般在应用启动时候就加载。
3、单例模式是一个设计思想。
1.5、什么是单例模式的延迟加载或早期加载?你是如何实现它的?
答:延迟加载就是使用时候采取创建对象出来,比如:懒汉式单例模式。
早期加载:比如:饿汉式单例模式,应用启动时就加载。
1.6、如何阻止使用clone()方法创建单例实例的另一个实例(如何防止单例被破坏)?
答:我们都知道JAVA里面所有的都继承Object类,防止破坏有两种方法。
第一种:将类定义为final类型,不让该类被继承。
第二种:如果单例类继承其它类的话,重写它的clone()方法,让他抛异常。
1.7、线程安全问题?
答:饿汉式是线程安全的,因为static属于类资源,类资源在jvm加载类时候就加载好了。
懒汉式会在多线程的情况下时创建两个对象,所以是线程不安全的。
二、单例模式案例
2.1、单例模式分类
主要分类:
饿汉式:在应用启动时就加载实例化对应、不管你是不使用(典型是空间换时间)。
懒汉式:在使用时候才会去创建(典型的时间换空间)。
懒汉式双重检查:弥补懒汉式的缺点。
2.2、代码实现
2.2.1、饿汉式
优点:是线程安全的,因为是在应用创建时候加加载类。
缺点:初始化是加载了,但是不保证该对象能被使用上,加入不使用的话就是浪费内存空间了
2.2.2、懒汉模式
我们知道饿汉模式中创建的对象如果创建出来不使用的话就会浪费内存空间,所以引出了懒汉单例模式,懒就是在使用的时候才回去创建,直接上代码。
优点:在使用时候才会去创建对象。
缺点:并发情况下可能会创建同时创建两个对象,系统依旧会浪费锁的开销。
方案一:
package co.debug.create.singletion.demo_02;
/**
* 懒汉单例模式,在使用时候再去实例化对象
* 缺点:因为getSingleton()使用synchronized 每次进来都会访问,浪费锁的开销
*
* @author Debug @2018-7-5
*/
class Singleton {
private static Singleton singleton;
/* 获取单例实例化对象 */
public static synchronized Singleton getSingleton() {
if (singleton == null) {
singleton = new Singleton();
}
System.out.println("线程名字:" + Thread.currentThread().getName());
return singleton;
}
}
以上的代码存在缺点:浪费锁的开销
方案二:
改进方法
package co.debug.create.singletion.demo_02;
/**
* 懒汉单例模式,在使用时候再去实例化对象 缺点:因为getSingleton()使用synchronized 每次进来都会访问,浪费锁的开销
*
* @author Debug @2018-7-5
*/
class Singleton {
private static Singleton singleton;
/* 获取单例实例化对象 */
public static Singleton getSingleton() throws InterruptedException {
if (singleton == null) {//1、阶段一
synchronized (Singleton.class) {//2、阶段二
singleton = new Singleton();//3、阶段三
}
}
return singleton;
}
}
经过改进之后,我把synchronized方法放置到null判断之后,在很多人看来这是很好的处理方法,相对于方案一来说节省了锁的开销,但是有一个问题,也就是在多线程或者高并发情况下可能创建两个或者多个singleton对象,违背了单例模式原则,加入:
这种写法减少了锁开销,但是在如下情况,却创建了2个对象:
a:线程1执行到1挂起,线程1认为singleton为null
b:线程2执行到1挂起,线程2认为singleton为null
c:线程1被唤醒执行synchronized块代码,走完创建了一个对象
d:线程2被唤醒执行synchronized块代码,走完创建了另一个对象
所以看出这种写法,并不完美。
——如果大家看到这里之后还不明白这是什么意思,那好我下面就给出了测试用例,让你彻底明白上面用法的缺欠,
要测试多线程情况的哈,Singleton方法就得添加线程管控了,代码修改如下
package co.debug.create.singletion.demo_02;
/**
* 懒汉单例模式,在使用时候再去实例化对象 缺点:因为getSingleton()使用synchronized 每次进来都会访问,浪费锁的开销
*
* @author Debug @2018-7-5
*/
class Singleton {
private static Singleton singleton;
/* 获取单例实例化对象 */
public static Singleton getSingleton() throws InterruptedException {
System.out.println("当前线程:" + Thread.currentThread().getName());
if (singleton == null) {
if ("Thread-0".equals(Thread.currentThread().getName())) {
Thread.sleep(20000);
}
if ("Thread-2".equals(Thread.currentThread().getName())) {
Thread.sleep(10000);
}
synchronized (Singleton.class) {
singleton = new Singleton();
System.out.println(Thread.currentThread().getName() + "---创建出来的地址:"+singleton);
}
}
return singleton;
}
}
测试类、创建多个线程去执行
package co.debug.create.singletion.demo_02;
/**
* 创建线程:对单例模式进行多线程测试
* @author Debug @2018-7-6
*/
public class TreadTest implements Runnable{
public void run() {
try {
Singleton.getSingleton();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread thread_01 = new Thread(new TreadTest());
Thread thread_02 = new Thread(new TreadTest());
Thread thread_03 = new Thread(new TreadTest());
thread_01.start();
thread_02.start();
thread_03.start();
}
}
测试结果:
结论:通过测试类的测试结果很明显的看出之后再多线程情况下会创建多个singleton对象。
方案三:双重检查锁
单例类:
package co.debug.create.singletion.demo_03;
/**
* 单例模式 :双重检查
*
* @author Debug @2018-7-6
*/
public class Singleton {
private static Singleton singleton;
public static Singleton newSingletion() throws InterruptedException {
System.out.println("当前线程:" + Thread.currentThread().getName());
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
System.out.println(Thread.currentThread().getName()+ "---创建");
}
}
}
return singleton;
}
public static Singleton getSingletion() throws InterruptedException {
return newSingletion();
}
}
线程测试类
package co.debug.create.singletion.demo_03;
/**
* 创建线程:对单例模式进行多线程测试
* @author Debug @2018-7-6
*/
public class TreadTest implements Runnable{
public void run() {
try {
Singleton.newSingletion();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread thread_01 = new Thread(new TreadTest());
Thread thread_02 = new Thread(new TreadTest());
Thread thread_03 = new Thread(new TreadTest());
Thread thread_04 = new Thread(new TreadTest());
Thread thread_05 = new Thread(new TreadTest());
Thread thread_06 = new Thread(new TreadTest());
Thread thread_07 = new Thread(new TreadTest());
Thread thread_08 = new Thread(new TreadTest());
thread_01.start();
thread_02.start();
thread_03.start();
thread_04.start();
thread_05.start();
thread_06.start();
thread_07.start();
thread_08.start();
}
}
测试结果:在多线程的情况下始终只创建一个对象
在同步锁代码块内部,再判断一次对象是否为null,为null才创建对象。这种写法已经接近完美:
a:线程1执行到1,已经进入synchronized的时候,线程挂起,线程1占有Singleton.class资源锁;
b:线程2执行到1,当它准备synchronized块时,因为Singleton.class被占用,线程2阻塞;
c:线程1被唤醒,判断出对象为null,执行完创建一个对象
d:线程2被唤醒,判断出对象不为null,不执行创建语句
结果:如果你在面试中能这样分析的话就非常的过关了