单例模式

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并且提供一个全局访问点。属于创建型模式。在 J2EE 标准中,ServletContext、 ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接 池也都是单例形式。

一.饿汉式单例

饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题。 Spring 中 IOC 容器 ApplicationContext 本身就是典型的饿汉式单例。

优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。

缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅 坑不拉屎。

 

package hungry;

import java.io.Serializable;

/**
 * 饿汉模式:类加载的时候直接创建实例,需要的时候直接返回实例对象
 */
public class HungrySingleton implements Serializable {
    //1.恶汉第一种方式:静态属性
    private static HungrySingleton hungrySingleton = new HungrySingleton();//类加载的时候执行,有且只执行一次
    //2.恶汉第二种:静态代码块
/*    static{
        hungrySingleton = new HungrySingleton();
        System.out.println("HungrySingleton静态代码块执行hungrySingleton="+hungrySingleton);
    }*/
    private HungrySingleton(){
        System.out.println("HungrySingleton私有无参构造执行.......");
    };

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    /**
     * 阻止,反序列化时破坏单例模式
     * @return
     */
    private Object readResolve(){
        return hungrySingleton;
    }
}

二.懒汉式单例

懒汉式单例的特点是:被外部类调用的时候内部类才会加载,存在线程安全问题。

package lazy;

import java.io.Serializable;

/**
 * 懒汉单例:
 *      需要的时候才创建实例
 */
public class LazySingleton{
    private static LazySingleton lazySingleton = null;

    private LazySingleton(){};
    //非线程安全
    public static LazySingleton getInstance(){
        if (null == lazySingleton){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
    //线程安全:方法加锁
    public synchronized static LazySingleton getInstance1(){
        if (null == lazySingleton){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

    //线程安全:代码块加锁,双重校验
    public static LazySingleton getInstance2(){
        if (null == lazySingleton){
            synchronized (LazySingleton.class){
                if (null == lazySingleton){
                    lazySingleton = new LazySingleton();
                    //分配内存
                    //初始化对象
                    //设置lazySingleton指向内存地址
                }
            }
        }
        return lazySingleton;
    }
}

饿汉式内部类 

package lazy;

/**
 * 饿汉式内部类:
 *         既兼顾了饿汉式的内存浪费,也兼顾了synchronized的安全与性能问题。
 */
public class LazyInnerClassSingleton {
    private LazyInnerClassSingleton(){
        //防止反射破坏单例模式
        if (null != LazyHolder.LAZY){
            throw new RuntimeException("LazyInnerClassSingleton不允许创建多个实例");
        }
    };

    public static LazyInnerClassSingleton getIntance(){
        return LazyHolder.LAZY;
    }

    /**
     * 内部类默认不加载,只有在使用的时候才加载
     */
    private static class LazyHolder{
        private static LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

多线程测试是否线程安全

package lazy;

public class LazyExecutor implements Runnable{
    public void run() {
       // LazySingleton instance = LazySingleton.getInstance2();
        LazyInnerClassSingleton instance = LazyInnerClassSingleton.getIntance();
        long time = System.currentTimeMillis();
        System.out.println("[time"+time+"]"+Thread.currentThread().getName()+":"+instance);
    }
}

 

package lazy;

public class LazySingletonTest {
    public static void main(String[] args) {
        LazyExecutor lazyExecutor = new LazyExecutor();
        for (int i = 0; i <10 ; i++) {
            new Thread(lazyExecutor).start();
        }
    }
}

 

破坏单例模式的两种方法

package lazy;

import hungry.HungrySingleton;

import java.io.*;
import java.lang.reflect.Constructor;

public class BrokeLazyInnerClassSingleton {


    public static void main(String[] args) {
        //brokeByReflect();
        brokeBySerializable();
    }

    /**
     * 通过反射强行访问私有构造方法,破坏单例模式
     */
    private static void brokeByReflect(){
        Class<?> clzz = LazyInnerClassSingleton.class;
        Constructor<?> cs = null;
        Object obj1 = null;
        try {
            cs = clzz.getDeclaredConstructor(null);
            cs.setAccessible(true);
            obj1 = cs.newInstance();
            System.out.println(obj1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        LazyInnerClassSingleton obj2 = LazyInnerClassSingleton.getIntance();
        System.out.println(obj2);
    }

    /**
     *  通过反序列化破坏单例模式
     **/
    private static void brokeBySerializable(){
        HungrySingleton obj1 = HungrySingleton.getInstance();
        System.out.println(obj1);
        HungrySingleton obj2 = null;
        try {
            FileOutputStream os = new FileOutputStream("HungrySingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(os);
            oos.writeObject(obj1);
            oos.flush();
            oos.close();
            os.close();

            FileInputStream fis = new FileInputStream("HungrySingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            //重写readResolve()
            obj2 = (HungrySingleton)ois.readObject();
            ois.close();
            fis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(obj2);
        System.out.println(obj1==obj2);
    }
}

 

三.注册式单例

注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记。

枚举式单例

天生的线程安全。枚举式单例在静态代码块中就给 INSTANCE 进行了赋值,是饿汉式单例的实现。

不能 用反射来创建枚举类型。进入 Constructor 的 newInstance()方法:在 newInstance()方法中做了强制性的判断,如果修饰符是 Modifier.ENUM 枚举类型, 直接抛出异常。

package register;

/**
 * 枚举式单例
 *      不能被反射和反序列化破坏单例模式
 */
public enum EnumSingle {
    INSTANCE;

    private Object data;

    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }

    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}

容器式单例

package register;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ContainerSingleton {
    private ContainerSingleton(){};

    private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();

    /**
     * 存在线程问题,所以要使用synchronized加锁
     * @param className
     * @return
     */
    public static Object getBean(String className){
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                try {
                    Class<?> clazz = Class.forName(className);
                    Object obj = clazz.newInstance();
                    ioc.put(className, obj);
                    return obj;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return ioc.get(className);
        }
    }



}

 

四.ThreadLocal 本地线程变量实现单例模式

ThreadLocal 不能保证其 创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。

package threadlocal;

/**
 * 天生线程内安全
 *      保证单线程内单例模式的实现
 */
public class ThreadLocalSingleton {

    private static final ThreadLocal<ThreadLocalSingleton> threadLocal = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue(){
            return new ThreadLocalSingleton();
        }
    };

    private ThreadLocalSingleton(){};

    public static ThreadLocalSingleton getInstance(){
        return threadLocal.get();
    }
}

多线程测试

package threadlocal;

public class ThreadLocalTest {
    public static void main(String[] args) {

        for (int i = 0; i <20 ; i++) {
            new Thread() {
                @Override
                public void run(){
                    System.out.println(Thread.currentThread().getName()+":start run.......");
                    ThreadLocalSingleton bean1 = ThreadLocalSingleton.getInstance();
                    ThreadLocalSingleton bean2 = ThreadLocalSingleton.getInstance();
                    ThreadLocalSingleton bean3 = ThreadLocalSingleton.getInstance();
                    ThreadLocalSingleton bean4 = ThreadLocalSingleton.getInstance();
                    ThreadLocalSingleton bean5 = ThreadLocalSingleton.getInstance();
                    System.out.println(Thread.currentThread().getName()+":"+bean1);
                    System.out.println(Thread.currentThread().getName()+":"+bean2);
                    System.out.println(Thread.currentThread().getName()+":"+bean3);
                    System.out.println(Thread.currentThread().getName()+":"+bean4);
                    System.out.println(Thread.currentThread().getName()+":"+bean5);
                    System.out.println(Thread.currentThread().getName()+":end run.......");
                }
            }.start();
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.fillInStackTrace();
            }

        }

    }
}

 

测试结果可以看出,在每个线程内可实现单例模式。

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值