JAVA-单例模式

分类:

懒汉式和饿汉式

单例应用场景:

(1)单体应用计数器

(2)应用配置、线程池

(3)数据库连接池

优点:

(1)在内存里只有一个实例,减少了内存开销

(2)可以避免对资源的多重占用

(3)设置全局访问点,严格控制访问,即:对外不能new

缺点:

没有接口,扩展困难

1)懒汉式:可延时加载实例,减小内存开销

1.1多线程不安全的懒汉式代码

单例类LazySingleton

package com.geely.design.pattern.creational.singleton;

/**
 * Created by geely
 */
public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    private LazySingleton(){
    }
    public static LazySingleton getInstance(){
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

}

线程类T

package com.geely.design.pattern.creational.singleton;

/**
 * Created by geely
 */
public class T implements Runnable {
    @Override
    public void run() {
        LazySingleton lazySingleton = LazySingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+"  "+lazySingleton);

    }
}

测试类Test

package com.geely.design.pattern.creational.singleton;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by geely
 */
public class Test {
    public static void main(String[] args) {
        Thread t1 = new Thread(new T());
        Thread t2 = new Thread(new T());
        t1.start();
        t2.start();
        System.out.println("program end");

    }
}

线程不安全说明:

通过idea 多线程debug模拟,具体方法参考:https://www.jianshu.com/p/3832ef4932d3

当线程1和线程2走到LazySingleton类的 lazySingleton = new LazySingleton();代码块时,

(1)假设线程1先执行了new LazySingleton操作,此时并没有结束,这时线程2也执行了new操作,由于lazySingleton实例是static的共享的,当线程2和线程1执行完后,线程2创建的对象会覆盖线程1创建的对象,如果在大并发下,可能会创建多次对象,非常消耗内存。

(2)假设线程1执行了new LazySingleton操作并结束,这时线程2开始执行new操作并结束,此时会存在两个不同的对象,不符合单例定义。

1.2 通过 synchronized关键字给静态方法加锁,实现线程安全

package com.geely.design.pattern.creational.singleton;

/**
 * Created by geely
 */
public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    private LazySingleton(){
    }
    public synchronized static LazySingleton getInstance(){
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

}

缺点:虽然解决了线程安全问题,但是synchronized 作用于整个类上,加锁和释放锁本身就会消耗性能,所以这种方式性能不好。

1.3 优化synchronized,通过 DoubleCheck双重检查 解决线程安全

单例类 LazyDoubleCheckSingleton

package com.geely.design.pattern.creational.singleton;

/**
 * Created by geely
 */
public class LazyDoubleCheckSingleton {
    private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
    private LazyDoubleCheckSingleton(){

    }
    public static LazyDoubleCheckSingleton getInstance(){
        if(lazyDoubleCheckSingleton == null){
            synchronized (LazyDoubleCheckSingleton.class){
                if(lazyDoubleCheckSingleton == null){
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                    //1.分配内存给这个对象
//                  //3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
                    //2.初始化对象
//                    intra-thread semantics
//                    ---------------//3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}

缺点:double check方案可以实现线程安全,且降低内存消耗。代码中三行注释代表了new对象时底层进行了三步操作,由于JVM优化算法可能会指令重排,也就是第二步和第三步执行的顺序会互换。这样可能会出现第一个线程出现了指令重排情况,先有内存地址但还没初始化对象,此时第二个线程获取到lazyDoubleCheckSingleton实例地址不为空,直接就返回实例对象,导致获取的对象是null并抛出异常。

下图是JVM执行new对象时,多线程情况下可能出现的步骤

1.4 DoubleCheck双重检查添加volatile关键字修饰静态类,禁止指令重排

private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

1.5 也可以使用静态内部类的初始化延迟加载解决,静态内部类有初始化锁,达到线程安全的目的

package com.geely.design.pattern.creational.singleton;

/**
 * Created by geely
 */
public class StaticInnerClassSingleton {
    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }
}

2)饿汉式:JVM启动就加载实例,不能延时加载

2.1 属性直接new

package com.geely.design.pattern.creational.singleton;

import java.io.Serializable;

/**
 * Created by geely
 */
public class HungrySingleton{

    private final static HungrySingleton hungrySingleton = new HungrySingleton();

   
    private HungrySingleton(){

    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

2.2 通过静态代码块new

package com.geely.design.pattern.creational.singleton;

/**
 * Created by geely
 */
public class HungrySingleton{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){

    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

}

其它:

1)序列化破坏单例模式

单例类 HungrySingleton

package com.geely.design.pattern.creational.singleton;

import java.io.Serializable;

/**
 * Created by geely
 */
public class HungrySingleton implements Serializable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

测试类 Test

package com.geely.design.pattern.creational.singleton;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by geely
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        HungrySingleton instance = HungrySingleton.getInstance();

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(instance);

        File file = new File("singleton_file");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));

        HungrySingleton newInstance = (HungrySingleton) ois.readObject();


        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);


    }
}

运行Test控制台输出结果
 

com.geely.design.pattern.creational.singleton.HungrySingleton@5c647e05
com.geely.design.pattern.creational.singleton.HungrySingleton@4c873330
false

由于:Test类中的 ois.readObject()会通过反射创建对象,所以与原对象不同

修复序列化破坏单例代码 HungrySingleton,添加 readResolve方法

package com.geely.design.pattern.creational.singleton;

import java.io.Serializable;

/**
 * Created by geely
 */
public class HungrySingleton implements Serializable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    private Object readResolve(){
        return hungrySingleton;
    }

}

运行Test控制台输出结果
 

com.geely.design.pattern.creational.singleton.HungrySingleton@5c647e05
com.geely.design.pattern.creational.singleton.HungrySingleton@5c647e05
true

2)单例模式反射攻击

只针对饿汉式,即类加载就创建对象。

懒汉式无法解决反射攻击,原因在于,懒汉式无法控制反射创建对象和自身创建对象的顺序,如果先反射后自身创建,则会有不同对象,如果先自身创建,再反射创建,可以在私有构造器抛异常。

单例类 HungrySingleton

package com.geely.design.pattern.creational.singleton;

import java.io.Serializable;

/**
 * Created by geely
 */
public class HungrySingleton implements Serializable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){

    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    private Object readResolve(){
        return hungrySingleton;
    }

  
}

Test类

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class objectClass = HungrySingleton.class;
        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
        HungrySingleton instance = HungrySingleton.getInstance();
        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);
    }
}

运行输出结果,反射出来了新实例

com.geely.design.pattern.creational.singleton.HungrySingleton@15db9742
com.geely.design.pattern.creational.singleton.HungrySingleton@6d06d69c
false

修复反射破坏,在HungrySingleton 私有构造方法添加逻辑

public class HungrySingleton implements Serializable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){
        if(hungrySingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    private Object readResolve(){
        return hungrySingleton;
    }


}

运行输出结果,控制台抛异常

xception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.geely.design.pattern.creational.singleton.Test.main(Test.java:71)
Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用
	at com.geely.design.pattern.creational.singleton.HungrySingleton.<init>(HungrySingleton.java:17)
	... 5 more

3)Enum枚举单例

解决序列化和反射攻击的最佳单例实现

3.1序列化例子

枚举类 EnumInstance

public enum EnumInstance {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
    public static EnumInstance getInstance(){
        return INSTANCE;
    }

}

Test类

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumInstance instance = EnumInstance.getInstance();
        instance.setData(new Object());
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(instance);
        File file = new File("singleton_file");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        EnumInstance newInstance = (EnumInstance) ois.readObject();

        System.out.println(instance.getData());
        System.out.println(newInstance.getData());
        System.out.println(instance == newInstance);
    }
}

控制台输出结果

java.lang.Object@119d7047
java.lang.Object@119d7047
true

3.2反射攻击例子

Test类

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class objectClass = EnumInstance.class;
        Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true); //打开私有构造方法的private权限
        EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);
        System.out.println(instance);
    }
}

控制台输出结果

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.geely.design.pattern.creational.singleton.Test.main(Test.java:59)
[ERROR] Command execution failed.

3.3另外枚举类中还可以写方法供外部调用

枚举类 EnumInstance

public enum EnumInstance {
    INSTANCE{
        // 枚举类中声明方法
        protected  void printTest(){
            System.out.println("Geely Print Test");
        }
    };
    // 外部调用的话需要声明
    protected abstract void printTest();
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
    public static EnumInstance getInstance(){
        return INSTANCE;
    }
}

Test类

public class Test {
    public static void main(String[] args)  {
        EnumInstance instance = EnumInstance.getInstance();
        instance.printTest();
    }
}

控制台输出

Geely Print Test

4)容器单例

容器单例类hashmap

public class ContainerSingleton {
    private ContainerSingleton(){}
    private static Map<String,Object> singletonMap = new HashMap<String,Object>();

    public static void putInstance(String key,Object instance){
        if(StringUtils.isNotBlank(key) && instance != null){
            if(!singletonMap.containsKey(key)){
                singletonMap.put(key,instance);
            }
        }
    }
    public static Object getInstance(String key){
        return singletonMap.get(key);
    }
}

线程类

public class T implements Runnable {
    @Override
    public void run() {
        ContainerSingleton.putInstance("object",new Object());
        Object instance = ContainerSingleton.getInstance("object");
        System.out.println(Thread.currentThread().getName()+"  "+instance);

    }
}

测试类

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Thread t1 = new Thread(new T());
        Thread t2 = new Thread(new T());
        t1.start();
        t2.start();
        System.out.println("program end");
    }
}

运行结果:

优点:对多个单例对象进行缓存统一管理;

缺点:线程不安全,hashmap在多线程不安全,可能会出现两个不同对象,或者创建对象两次,最后一个对象把前一个对象覆盖的现象。结果复现参考:1.1多线程不安全的懒汉式代码

5)ThreadLocal线程单例

ThreadLocalInstance类

public class ThreadLocalInstance {
    private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal
             = new ThreadLocal<ThreadLocalInstance>(){
        @Override
        protected ThreadLocalInstance initialValue() {
            return new ThreadLocalInstance();
        }
    };
    private ThreadLocalInstance(){

    }

    public static ThreadLocalInstance getInstance(){
        return threadLocalInstanceThreadLocal.get();
    }

}

 线程类

public class T implements Runnable {
    @Override
    public void run() {
        ThreadLocalInstance instance = ThreadLocalInstance.getInstance();
        System.out.println(Thread.currentThread().getName()+"  "+instance);

    }
}

 测试类

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        System.out.println("main thread"+ThreadLocalInstance.getInstance());
        System.out.println("main thread"+ThreadLocalInstance.getInstance());
        System.out.println("main thread"+ThreadLocalInstance.getInstance());
        System.out.println("main thread"+ThreadLocalInstance.getInstance());
        System.out.println("main thread"+ThreadLocalInstance.getInstance());
        System.out.println("main thread"+ThreadLocalInstance.getInstance());

        Thread t1 = new Thread(new T());
        Thread t2 = new Thread(new T());
        t1.start();
        t2.start();
        System.out.println("program end");
    }
}

运行结果

main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
program end
Thread-0  com.geely.design.pattern.creational.singleton.ThreadLocalInstance@26484d1e
Thread-1  com.geely.design.pattern.creational.singleton.ThreadLocalInstance@33918ac4

文章内容参考自慕课网

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值