Java设计模式——单例模式
我一直觉得,设计模式的思想都是源于生活的。单例在生活之中也是经常用到的,比如国家领导人、某某公司创始人......类似这种独一无二的。单例模式也属于创建型设计模式,确保在任何情况下单例类最多只能有一个实例对象,并且提供全局访问点。单例模式可以保证内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用。反正就是在内存世界里,单例模式的类的实例是独一无二的。当然也有线程单例,就是同一个线程只有一个单例类实例。
饿汉式单例
类加载的时候就立马初始化生成类的实例对象。不管来没来,先吃饱再说。
步骤:
- 提供一个单例类的私有的最终的静态全局单例类属性变量。
- 私有化单例类的构造方法。
- 初始化对象(可以在第一步或者第二步完成)。
- 提供全局访问点,用于返回对象实现。
代码:
/**
* @description: 饿汉式单例
* @author: lmc
* @create: 2019-04-02 20:39
**/
public class HungrySingletonOne implements Serializable {
/**
* 饿汉式单例,在类初始化的时候就进行对象的创建,不存在不同步问题
*/
//第一步提供一个私有的最终的静态全局属性变量,用于返回对象。
private static final HungrySingletonOne hungrySingletonOne=new HungrySingletonOne();
//第二步 私有化构造方法
private HungrySingletonOne(){
if(null != hungrySingletonOne){
throw new RuntimeException("单例类,不允许被反射实例化");
}
}
//提供全局访问点
public static HungrySingletonOne getInstance(){
return hungrySingletonOne;
}
/**
* @description: 重写readResolve方法,防止序列化破坏单例
* @return java.lang.Object
* @date 2019/5/25 22:12
* @author lmc
*/
private Object readResolve(){
return hungrySingletonOne;
}
}
/**
* @description: 饿汉式单例
* @author: lmc
* @create: 2019-04-02 20:39
**/
public class HungrySingletonTwo {
/**
* 饿汉式单例,在类初始化的时候就进行对象的创建
*/
//第一步提供一个私有的最终的静态全局属性变量,用于返回对象。
private static final HungrySingletonTwo hungrySingletonTwo;
static {
hungrySingletonTwo=new HungrySingletonTwo();
}
//第二步 私有化构造方法
private HungrySingletonTwo(){
if(null != hungrySingletonTwo){
throw new RuntimeException("单例类,不允许被反射实例化");
}
}
//提供全局访问点
public static HungrySingletonTwo getInstance(){
return hungrySingletonTwo;
}
/**
* @description: 重写readResolve方法,防止序列化破坏单例
* @return java.lang.Object
* @date 2019/5/25 22:12
* @author lmc
*/
private Object readResolve(){
return hungrySingletonTwo;
}
}
饿汉式单例利弊
利:没有加锁,执行效率高,比懒汉式体验好。饿汉式,在单例类加载的时候就已经初始化好了实例对象,不存在线程安全问题,因此没有共享的说法。
弊:如果单例类不经常使用,占用了内存。
Spring中 IOC
容器ApplicationContext
本身就是典型的饿汉式单例
饿汉式单例不存在线程安全问题,这里就不做测试结果展示了,上面的代码都是可以直接运行测试的。
懒汉式单例之双重检查锁单例
懒汉式单例类在类加载的时候不会初始化类生成单例类的实例,而是在调用单例类获取实例的方法的时候才会去初始化实例对象,并返回一个单例对象。
因为懒汉式单例,是在单例类的获取实例方法被调用的时候才会去初始化对象,所以存在高并发,线程安全问题。
synchronized用来保证线程安全问题(原理在这里不细说了)
根据业务需求,synchronized关键字能不写在方法上就不要写在方法上,写在方法里面。这样可以避免整个类都被锁住,写在方法里面,其他线程还是能够运行这个方法被锁之前的代码的。性能稍微提供提高一点点。所以我们有了双重检查锁单例。
/**
* @description: 简单的懒汉式单例
* @author: lmc
* @create: 2019-04-03 08:59
**/
public class LazySimpleSingleton implements Serializable {
//第一步 构造方法私有化,并且设置异常防止反射破坏单例
private LazySimpleSingleton(){
if(null != lazySimpleSingleton){
throw new RuntimeException("单例类,不允许被反射实例化");
}
}
//第二步 定义对象属性
private static volatile LazySimpleSingleton lazySimpleSingleton=null;
//第三步 声明全局访问点
/**
* @description: 双重检查锁单例
* @return com.lmc.gp12380.pattern.singleton.lazy.LazySimpleSingleton
* @date 2019/5/25 21:21
* @author lmc
*/
public static LazySimpleSingleton getInstance(){
if(null == lazySimpleSingleton){//第一次检查
synchronized (LazySimpleSingleton.class){//加锁 保证线程安全性
if(null == lazySimpleSingleton){//第二次检查
lazySimpleSingleton=new LazySimpleSingleton();
}
}
}
return lazySimpleSingleton;
}
/**
* @description: 重写readResolve方法,防止序列化破坏单例
* @return java.lang.Object
* @date 2019/5/25 22:12
* @author lmc
*/
private Object readResolve(){
return lazySimpleSingleton;
}
}
双重检查锁单例,可以用 idea 的debug线程模式调试测试。
volatile关键字的作用在这里不细说,就是用来保证绝对的线程安全。
定义一个线程执行器
/**
* @description: 执行线程
* @author: lmc
* @create: 2019-04-03 08:56
**/
public class ExectorThread implements Runnable {
public void run() {
LazySimpleSingleton lazySimpleSingleton=LazySimpleSingleton.getInstance();
System.out.println("线程"+Thread.currentThread().getName()+"lazySimpleSingleton"+lazySimpleSingleton);
}
}
测试代码
/**
* @description: 简单懒汉式单例测试
* @author: lmc
* @create: 2019-04-03 09:10
**/
public class LazySimpleSingletonTest {
public static void main(String[] args){
for (int i = 0; i <10; i++) {
Thread thread= new Thread(new ExectorThread());
thread.start();
}
}
}
测试结果
可以看到,10个线程获取的对象都是同一个实例。
懒汉式单例之静态内部类单例
静态内部类单例在外部类调用获取实例方法的时候才会初始化实例对象,静态内部类在类加载的时候并不会初始化,只有在创建内部类对象或者,内部类对象静态成员被第一次引用的时候才会初始化对象。然而,对于静态内部类单例来说,我们永远不会主动的去创建内部类对象。
/**
* @description: 懒汉式静态内部类单例
* @author: lmc
* @create: 2019-04-03 11:28
**/
public class LazyInnerClassSingleton implements Serializable {
//私有化构造方法
private LazyInnerClassSingleton(){
if(Holder.lazy != null){//只能调用一次构造创建实例
throw new RuntimeException("静态内部类单例,不允许创建多个实例");
}
}
public static final LazyInnerClassSingleton getInstance(){
return Holder.lazy;
}
/**
* @description:静态内部类初始化外部类成员变量
* @date 2019/5/25 22:11
* @author lmc
*/
private static class Holder {
private final static LazyInnerClassSingleton lazy=new LazyInnerClassSingleton();
}
/**
* @description: 重写readResolve方法,防止序列化破坏单例
* @return java.lang.Object
* @date 2019/5/25 22:12
* @author lmc
*/
private Object readResolve(){
return Holder.lazy;
}
}
上面的代码中,内部类Holder的静态成员变量 lazy是 final static修饰,无论是创建内部类对象,初始化lazy还是调用静态属性lazy引用初始化都之后初始化一次。并且LazyInnerClassSingleton
单例类只能被内部类实例化一次。
测试代码
/**
* @description: 内部类懒汉式单例测试
* @author: lmc
* @create: 2019-04-03 09:10
**/
public class LazyInnerClassSingletonTest {
public static void main(String[] args){
for (int i = 0; i <10 ; i++) {
Thread thread= new Thread(new Runnable() {
@Override
public void run() {
LazyInnerClassSingleton lazyInnerClassSingleton = LazyInnerClassSingleton.getInstance();
System.out.println("线程"+Thread.currentThread().getName()+"lazyInnerClassSingleton"+lazyInnerClassSingleton);
}
});
thread.start();
}
}
}
测试结果
注册式单例之容器单例
/**
* @description: 容器式单例
* @author: lmc
* @create: 2019-04-08 21:43
**/
public class ContainerSingleton {
private ContainerSingleton(){};
private static Map<String,Object> ioc=new ConcurrentHashMap<String, Object>();
public static Object getBean(String className){
if(null != className && className!=""){
synchronized (className){
if(ioc.containsKey(className)){
return ioc.get(className);
}
Object obj=null;
try {
obj=Class.forName(className).newInstance();
ioc.put(className,obj);
}catch (Exception e){
e.printStackTrace();
}
return obj;
}
}
return null;
}
}
如果需要创建很多单例对象,一般用容器式单例管理对象。
虽然 ConcurrentHashMap
是线程安全的,但是调用getBean
方法不是线程安全的,所有要加synchronized
锁。
容器式单例就不写测试结果了。
注册式单例之枚举单例
/**
* @description: 枚举单例
* @author: lmc
* @create: 2019-04-03 15:31
**/
public enum EnumSingleton {
INSTENCE;
private EnumSingleton(){
}
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTENCE;
}
}
枚举类型重JVM
虚拟机底层就帮我们做了防止序列化和反射破坏单例。
反编译EnumSingleton.class
文件
package com.lmc.gp12380.pattern.singleton.register;
public final class EnumSingleton extends Enum
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(com/lmc/gp12380/pattern/singleton/register/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public Object getData()
{
return data;
}
public void setData(Object data)
{
this.data = data;
}
public static EnumSingleton getInstance()
{
return INSTENCE;
}
public static final EnumSingleton INSTENCE;
private Object data;
private static final EnumSingleton $VALUES[];
static
{
INSTENCE = new EnumSingleton("INSTENCE", 0);
$VALUES = (new EnumSingleton[] {
INSTENCE
});
}
}
反编译之后枚举 INSTENCE
变成了静态最终变量,由静态代码块饿汉式初始化,EnumSingleton
构造函数也是私有的,不允许外部创建对象。所有枚举符合单例需求。
测试代码
/**
* @description: 枚举单例测试
* @author: lmc
* @create: 2019-04-03 15:33
**/
public class EnumSingletonTest {
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
EnumSingleton enumSingleton=EnumSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+"enumSingleton>>"+enumSingleton);
}
});
thread.start();
}
}
}
测试结果
反射和序列化破坏单例测试
上述代码中,饿汉式和懒汉式,在私有化构造方法中都是有条件抛出异常
if(condition){
throw new RuntimeException("单例类,不允许被反射实例化");
}
这段代码是为了保证单例类只能实例化一次,防止反射破坏单例。
private Object readResolve(){
return hungrySingletonOne;
}
上面的代码是重写了Serializable
接口的readResolve
方法,是为了防止序列化破坏单例对象。
防止反射破坏单例测试
/**
* @description: 反射破坏单例测试
* @author: lmc
* @create: 2019-05-25 23:19
**/
public class TestReflectDestructionSingleton {
public static void main(String[] args) {
try {
LazyInnerClassSingleton lazyInnerClassSingleton1= LazyInnerClassSingleton.getInstance();
System.out.println(lazyInnerClassSingleton1);
Class<?> clazz=LazyInnerClassSingleton.class;
Constructor c=clazz.getDeclaredConstructor(null);
c.setAccessible(true);
LazyInnerClassSingleton lazyInnerClassSingleton2= (LazyInnerClassSingleton) c.newInstance();
System.out.println(lazyInnerClassSingleton2);
System.out.println(lazyInnerClassSingleton1==lazyInnerClassSingleton2);
}catch (Exception e){
e.printStackTrace();
}
}
}
测试结果
利用反射生成实例,直接抛出异常,中断程序运行。客户端调用就不会去利用反射了。
去掉上面的抛出异常的条件执行测试程序,会出现两个不一样的实例,单例被破坏。
防止序列化破坏
/**
* @description: 序列化破坏单例测试
* @author: lmc
* @create: 2019-05-25 23:13
**/
public class TestSerializDestructionSingleton {
public static void main(String[] args) {
LazyInnerClassSingleton s1 = null;
LazyInnerClassSingleton s2 = LazyInnerClassSingleton.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("LazyInnerClassSingleton.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("LazyInnerClassSingleton.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (LazyInnerClassSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果
序列化和反序列化生成的对象和之前的对象是一样的,这就说明单例模式有效。
验证序列化破坏单例只需要去掉重写的readResolve
方法就可以得到两个不一样的实例,单例被破坏。
反射破坏单例测试和序列化破坏单例只需要更换类就能测试其他单例了,在这就不做测试了。
注册式容器式单例是从Map集合获取对象,不需要做单例破坏测试。
注册式枚举式单例是重JVM
层面防止单例破坏。虽然没有加上面的防止破坏代码,也可以用上面的测试代码测试。