单例模式
单例模式属于创建型模式的一种,他的写法有许多中常见的有饿汉模式、懒汉模式、登记式模式(未去研究,不做说明)等。
一:单例模式特点
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
二:单例模式分类
饿汉模式
懒汉模式
登记式模式
三:产生说明
我们平时在用类的对象实例时,使用new出来的,但是这样会出现不同的对象实例。
但是有时我们需要一个并且是唯一的一个对象实例,这样就总结出了单例模式。
如:
package com.zkrmfx.demo;
/*
* 不是单例
*/
public class NoSingleton {
}
package com.zkrmfx.test;
import org.junit.Test;
import com.zkrmfx.demo.EagerSingleton;
import com.zkrmfx.demo.LazySingleton;
import com.zkrmfx.demo.MyThead;
import com.zkrmfx.demo.NoSingleton;
public class SingletonTest {
@Test
public void noSingletonTest() {
NoSingleton singleton1 = new NoSingleton();
NoSingleton singleton2 = new NoSingleton();
System.out.println(singleton1);
System.out.println(singleton2);
// 测试结果 产生两个实例与我们要求产生一个实例需求不符
// com.zkrmfx.demo.NoSingleton@7daf6ecc
// com.zkrmfx.demo.NoSingleton@2e5d6d97
}
}
四:饿汉模式
package com.zkrmfx.demo;
/*
* 饿汉式单例模式
* 意义:利用空间去换时间
* 缺点:产生对象不知道会不会使用,占用空间即内存
*/
public class EagerSingleton {
// 不允许 new 出对象 ,构造方法私有化
private EagerSingleton() {
}
// 自己产生对象
private static EagerSingleton instance = new EagerSingleton();
// 自己能提供反馈这个对象的方法并且只能是静态方法 用类点方法去调用
public static EagerSingleton getInstance() {
return instance;
}
}
package com.zkrmfx.test;
import org.junit.Test;
import com.zkrmfx.demo.EagerSingleton;
import com.zkrmfx.demo.LazySingleton;
import com.zkrmfx.demo.MyThead;
import com.zkrmfx.demo.NoSingleton;
public class SingletonTest {
@Test
public void eagerSingletonTest() {
EagerSingleton singleton1 = EagerSingleton.getInstance();
EagerSingleton singleton2 = EagerSingleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
// 测试结果 产生一个单一实例
// com.zkrmfx.demo.EagerSingleton@5010be6
// com.zkrmfx.demo.EagerSingleton@5010be6
}
}
饿汉模式产生一个对象实例,由于他的对象实例是在类创建时就创建一个静态的对象实例。它将占用我们的内存,但是我们要用时随时可以去调用(即空间换时间)。
由于对象实例已经创建好,无论我们怎样去调用都是可以的,不存在线程安全问题。
五:懒汉式单列
package com.zkrmfx.demo;
/*
* 懒汉式单例模式
* 意义:对象在要用是才创建节约空间,即延迟加载策略
* 缺点:会产生线程安全问题
*/
public class LazySingleton {
// 不允许 new 出对象 ,构造方法私有化
private LazySingleton() {
}
private static LazySingleton instance = null;
// 自己能提供反馈这个对象的方法并且只能是静态方法 用类点方法去调用
public static LazySingleton getInstance() {
//如果为空,则new一个对象实例
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
package com.zkrmfx.demo;
/*
* 线程实现
*/
public class MyThead extends Thread {
@Override
public void run() {
System.out.println(LazySingleton.getInstance());
}
}
package com.zkrmfx.test;
import org.junit.Test;
import com.zkrmfx.demo.EagerSingleton;
import com.zkrmfx.demo.LazySingleton;
import com.zkrmfx.demo.MyThead;
import com.zkrmfx.demo.NoSingleton;
public class SingletonTest {
@Test
public void lazySingletonTest1() {
LazySingleton lazySingleton1 = LazySingleton.getInstance();
LazySingleton lazySingleton2 = LazySingleton.getInstance();
System.out.println(lazySingleton1);
System.out.println(lazySingleton2);
// 测试结果 产生一个实例 !!!多测试几次
// com.zkrmfx.demo.LazySingleton@5010be6
// com.zkrmfx.demo.LazySingleton@5010be6
}
@Test
public void lazySingletonTest2() {
MyThead singleton1 = new MyThead();
MyThead singleton2 = new MyThead();
singleton1.start();
singleton2.start();
// 测试结果 产生多个实例 !!!多测试几次
// com.zkrmfx.demo.LazySingleton@50229931
// com.zkrmfx.demo.LazySingleton@d90d8b9
}
}
从程序来看:我们在需要对象实例时才去创建,不想饿汉模式那样事先创建好,我们要用时才去创建,这样就避免了对象实例存在不使用的情况(延迟策略),但是时间相对来说要耗费的多一点。
lazySingletonTest1 测试结果:单线程测试时,对象实例是唯一
lazySingletonTest2 测试结果:多线程测试时,对象实例不唯一
以上结果说明懒汉式单例不是线程安全的,那如何解决呢?
六:解决懒汉式线程安全问题
方案一:由于产生原因是多线程并发,给getInstance() 方法加同步防止并发的产生。
方案二: 利用静态内部类加载时只加载一次从而只产生一个对象实例
package com.zkrmfx.demo;
/*
* 懒汉式单例模式
* 意义:对象在要用是才创建节约空间,即延迟加载策略
* 缺点:会产生线程安全问题
*/
public class LazySingleton {
// 线程安全解决方案一: 加synchronized同步
// 不允许 new 出对象 ,构造方法私有化
private LazySingleton() {
}
// 自己产生对象
private static LazySingleton instance = null;
// 自己能提供反馈这个对象的方法并且只能是静态方法 用类点方法去调用 加入synchronized 同步
public static synchronized LazySingleton getInstance() {
//如果不存在,则new一个
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
package com.zkrmfx.test;
import org.junit.Test;
import com.zkrmfx.demo.EagerSingleton;
import com.zkrmfx.demo.LazySingleton;
import com.zkrmfx.demo.MyThead;
import com.zkrmfx.demo.NoSingleton;
public class SingletonTest {
@Test
public void synchronizedLazySingletonTest() {
MyThead singleton1 = new MyThead();
MyThead singleton2 = new MyThead();
singleton1.start();
singleton2.start();
// 测试结果 产生一个实例 !!!多测试几次
// com.zkrmfx.demo.LazySingleton@479f9b98
// com.zkrmfx.demo.LazySingleton@479f9b98
}
}
方案二: 利用静态内部类加载时只加载一次从而只产生一个对象实例
package com.zkrmfx.demo;
/*
* 懒汉式单例模式
* 意义:对象在要用是才创建节约空间,即延迟加载策略
* 缺点:会产生线程安全问题
*/
public class LazySingleton {
// 线程安全解决方案二-----静态内部类
// 不允许 new 出对象 ,构造方法私有化
private LazySingleton() {
}
// 利用类加载时只 加载一次 从而只产生一个实例
private static class Lazy {
private static final LazySingleton INSTANCE = new LazySingleton();
}
// 自己能提供反馈这个对象的方法并且只能是静态方法 用类点方法去调用
public static LazySingleton getInstance() {
return Lazy.INSTANCE;
}
}
package com.zkrmfx.test;
import org.junit.Test;
import com.zkrmfx.demo.EagerSingleton;
import com.zkrmfx.demo.LazySingleton;
import com.zkrmfx.demo.MyThead;
import com.zkrmfx.demo.NoSingleton;
public class SingletonTest {
@Test
public void staticClassEagerSingletonTest() {
MyThead singleton1 = new MyThead();
MyThead singleton2 = new MyThead();
singleton1.start();
singleton2.start();
// 测试结果 产生一个实例 !!!多测试几次
// com.zkrmfx.demo.LazySingleton@4e08c2b2
// com.zkrmfx.demo.LazySingleton@4e08c2b2
}
}
方案三:添加双重检查锁定 (这是别人总结的东西 详细可以连接: http://blog.csdn.net/jason0539/article/details/23297037/)
package com.zkrmfx.demo;
/*
* 懒汉式单例模式
* 意义:对象在要用是才创建节约空间,即延迟加载策略
* 缺点:会产生线程安全问题
*/
public class LazySingleton {
// 线程安全解决方案三 : 双重检查锁定
// 双重检查锁定不是线程安全的,要用volatile关键字
// 不允许 new 出对象 ,构造方法私有化
private LazySingleton() {
}
// 未加volatile
private static LazySingleton instance = null;
// volatile修饰一个变量,而且是一个"容易变"的变量。
// 在每次取这个变量值的时候,要求不是取它上次在某个时候取
// 的临时缓存变量(比如说暂存在某个寄存器中),而是直接到内存中取。
// volatile变量能防止优化,别如说你在某个地方可能连续调用了好几
// 次这个函数,于是编译器优化后,可能就调用一次,其他几次就采用这一
// 次调用的返回值,而volatile修饰后,要让每一次都进行函数调用,
// 而不采用暂存值。
// 详细请链接 http://blog.csdn.net/jk198310/article/details/14222713
// private static volatile LazySingleton instance = null;
// 自己能提供反馈这个对象的方法并且只能是静态方法 用类点方法去调用
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
package com.zkrmfx.test;
import org.junit.Test;
import com.zkrmfx.demo.EagerSingleton;
import com.zkrmfx.demo.LazySingleton;
import com.zkrmfx.demo.MyThead;
import com.zkrmfx.demo.NoSingleton;
public class SingletonTest {
@Test
public void doubleCheckedLockingEagerSingletonTest1() {
MyThead singleton1 = new MyThead();
MyThead singleton2 = new MyThead();
singleton1.start();
singleton2.start();
// 测试结果 产生一个单一实例
// com.zkrmfx.demo.LazySingleton@7daf6ecc
// com.zkrmfx.demo.LazySingleton@7daf6ecc
}
@Test
public void doubleCheckedLockingEagerSingletonTest2() {
EagerSingleton singleton1 = EagerSingleton.getInstance();
EagerSingleton singleton2 = EagerSingleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
// 测试结果 产生一个单一实例
// com.zkrmfx.demo.LazySingleton@7daf6ecc
// com.zkrmfx.demo.LazySingleton@7daf6ecc
}
}
这种方式我找不到用不用volatile的区别,帖子没有给具体测试的方式,说的比较笼统。
我用了几种方法测试,都不能测出来 volatile 具体使用方式我还是没能搞清楚
不知道问题出在哪儿,还是自己知识面太窄了!!!
不知道缓存的问题望大神能给出测试的方法
六:单例模式应用
在Java jdk 中 Runtime 类就是单列类 getRuntime() 返回唯一对象实例
不要错误的认为利用类点 getInstance() 调用对象实例的就是单例类,Java jdk 中 Calenda r类就是getInstance()返回对象实例的,但不是单例类。
以下是我找到的双重检查锁定帖子说明:(看下一别的大神的解释)