设计模式-单例模式

某个对象全局只需要一个实例时,就可以使用单例模式。

开始学习单例的各种写法

第一种:简单懒汉式模式

/**
 * 第一种
 * 简单懒汉式
 */
public class Singleton {
    /**
     * 单例模式不能被别人new 所以访问权限改为私有。必须的
     */
    private Singleton(){}

    //懒汉式 不会先去new对象 而是在用的时候才回去创建 所以叫懒汉式
    private static Singleton singleton =null;

    //因为为全局唯一访问点 所以需要定义为static,属性也定义为static
    public static Singleton getInstance(){
        if(singleton==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}
public class SingletonTest {
    public static void main(String[] args) {
        //测试简单懒汉式
        //Singleton singleton = new Singleton();  方法私有不能创建
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance1);
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance2);
        Singleton instance3 = Singleton.getInstance();
        System.out.println(instance3);
    }
}
 运行结果:
com.shangjun.test02.Singleton@4554617c
com.shangjun.test02.Singleton@4554617c
com.shangjun.test02.Singleton@4554617c
 

 

  该模式有缺点,同一个线程中不会有问题,但在多线程的情况下会出现线程安全问题。  

  测试多线程情况

public class Singleton {
    /**
     * 单例模式不能被别人new 所以访问权限改为私有。必须的
     */
    private Singleton(){}

    //懒汉式 不会先去new对象 而是在用的时候才回去创建 所以叫懒汉式
    private static Singleton singleton =null;

    //因为为全局唯一访问点 所以需要定义为static,属性也定义为static
    public static Singleton getInstance(){
        if(singleton==null){
            try {
                Thread.sleep(10);//添加睡眠时间 时间越长更明显
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton = new Singleton();
        }
        return singleton;
    }
}
public class SingletonTest {
public static void main(String[] args) {
//测试多线程模式下懒汉式
for(int i=0;i<5;i++){
new Thread(()->{
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1);
}).start();
}
}
}
 //运行结果

com.shangjun.test02.Singleton@1b9c704e
com.shangjun.test02.Singleton@2ceb2de
com.shangjun.test02.Singleton@67c084e5
com.shangjun.test02.Singleton@47e3e4b5
com.shangjun.test02.Singleton@ae526cf

 

 

第二种:简单懒汉升级版 

  简单懒汉模式会有线程安全问题,所以要对访问资源进行加锁 访问方法添加synchronized关键字。

/**
 * 懒汉式
 */
public class Singleton {
    /**
     * 单例模式不能被别人new 所以访问权限改为私有。必须的
     */
    private Singleton(){}

    //懒汉式 不会先去new对象 而是在用的时候才回去创建 所以叫懒汉式
    //volatile 添加该关键字可以防止指令重排序 保证线程修改被其他线程知道,
    private volatile static Singleton singleton =null;

    //因为为全局唯一访问点 所以需要定义为static,属性也定义为static
    public synchronized static Singleton getInstance(){
        if(singleton==null){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton = new Singleton();
        }
        return singleton;
    }
}

public class SingletonTest {
public static void main(String[] args) {
//测试多线程模式下懒汉式
for(int i=0;i<5;i++){
new Thread(()->{
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1);
}).start();
}
}
}
 测试结果:

com.shangjun.test02.Singleton@2fd04fd1
com.shangjun.test02.Singleton@2fd04fd1
com.shangjun.test02.Singleton@2fd04fd1
com.shangjun.test02.Singleton@2fd04fd1
com.shangjun.test02.Singleton@2fd04fd1

 

懒汉式这种模式可以在使用的时候才建立对象,但是因为加锁的缘故,所以当并发比较多的时候会导致性能问题。因为每次访问该方法会等锁释放后才能够获得对象。

第三种:饿汉式模式

与懒汉式相对应的就是饿汉式,与懒汉式对比,在调用之前对象已经建立。不需要进行加锁,也就不存在性能问题。

/**
 * 饿汉式
 */
public class Singleton {
    /**
     * 单例模式不能被别人new 所以访问权限改为私有。必须的
     */
    private Singleton(){}

    //饿汉式 不管用不用,直接创建对象
    private static Singleton singleton = new Singleton();

    //因为为全局唯一访问点 所以需要定义为static,属性也定义为static
    public static Singleton getInstance(){
        return singleton;
    }
}
public class SingletonTest {
    public static void main(String[] args) {
        //测试多线程模式下饿汉式
        for(int i=0;i<5;i++){
            new Thread(()->{
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1);
            }).start();
        }
    }
}
测试结果:

com.shangjun.test02.Singleton@79a19a06
com.shangjun.test02.Singleton@79a19a06
com.shangjun.test02.Singleton@79a19a06
com.shangjun.test02.Singleton@79a19a06
com.shangjun.test02.Singleton@79a19a06

 

但是这种情况会直接创建好对象,启动的时候会耗费内存,耗费时间,所以有了第四种。

 第四种:双重检锁机制(double check locking 简称DCL)

可以进行懒加载,相当于懒汉式的加强版  进行两次判断 一次加锁完成 ,注意属性添加 volatile 防止指令重排序

/**
 * 双重检索机制 DCL
 */
public class Singleton {
    /**
     * 单例模式不能被别人new 所以访问权限改为私有。必须的
     */
    private Singleton(){}

    //懒汉式 不会先去new对象 而是在用的时候才回去创建 所以叫懒汉式
    //volatile 添加该关键字可以防止指令重排序 保证线程修改被其他线程知道
    private volatile static Singleton singleton = null;

    //因为为全局唯一访问点 所以需要定义为static,属性也定义为static
    public static Singleton getInstance(){
        //第一次进行检查判断
        if(singleton==null){
            //进行加锁
            synchronized (Singleton.class){
                //第二次进行检查判断
                if(singleton==null){
                    //创建对象
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
public class SingletonTest {
    public static void main(String[] args) {
        //测试多线程模式下饿汉式
        for(int i=0;i<5;i++){
            new Thread(()->{
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1);
            }).start();
        }
    }
}
运行结果:

com.shangjun.test02.Singleton@14550b42
com.shangjun.test02.Singleton@14550b42
com.shangjun.test02.Singleton@14550b42
com.shangjun.test02.Singleton@14550b42
com.shangjun.test02.Singleton@14550b42

 

 

 

第五种:静态内部类

/**
 * 静态内部类方式
 */
public class Singleton {
    /**
     * 单例模式不能被别人new 所以访问权限改为私有。必须的
     */
    private Singleton(){}

    private static class Holder{
        //volatile 添加该关键字可以防止指令重排序 保证线程修改被其他线程知道
        private volatile static Singleton singleton = new Singleton();
    }
    //因为为全局唯一访问点 所以需要定义为static,属性也定义为static
    public static Singleton getInstance(){
        return Holder.singleton;
    }
}
public class SingletonTest {
    public static void main(String[] args) {
        //测试多线程模式下饿汉式
        for(int i=0;i<5;i++){
            new Thread(()->{
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1);
            }).start();
        }
    }
}
运行结果:

com.shangjun.test02.Singleton@47e3e4b5
com.shangjun.test02.Singleton@47e3e4b5
com.shangjun.test02.Singleton@47e3e4b5
com.shangjun.test02.Singleton@47e3e4b5
com.shangjun.test02.Singleton@47e3e4b5

 

 

我们把Singleton实例放到一个静态内部类中,这样可以避免了静态实例在Singleton类的加载阶段(类加载过程的其中一个阶段的,此时只创建了Class对象,静态变量初始化是在Singleton类初始化时触发的,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全的。

第六种:枚举方式

public class Singleton {
}
public enum SingletonEnum {

    INSTANCE;
    private Singleton Singleton =new Singleton();
    public Singleton getInstance(){
        return Singleton;
    }

}
public class SingletonTest {
    public static void main(String[] args) {
        //测试多线程模式下饿汉式
        for(int i=0;i<5;i++){
            new Thread(()->{
//                Singleton instance1 = Singleton.getInstance();
                System.out.println(SingletonEnum.INSTANCE.getInstance());
            }).start();
        }
    }
}
测试结果:

com.shangjun.test02.Singleton@1b9c704e
com.shangjun.test02.Singleton@1b9c704e
com.shangjun.test02.Singleton@1b9c704e
com.shangjun.test02.Singleton@1b9c704e
com.shangjun.test02.Singleton@1b9c704e

 

 

posted @ 2020-11-13 10:07  程序员子期  阅读(64)  评论(0)    收藏  举报