设计模式-单例模式

1 单例模式

1.1 饿汉式

私有构造函数,禁止外部访问。
使用static和final做到类加载到内存后,就实例化一个单例,而且是唯一的,JVM保证线程安全。简单实用。
唯一缺点:不管用到与否,类装载时就完成实例化,占用内存。

1.1.1 写法1

package com.DesignPattern.singleton;

/*
饿汉式
类加载到内存后,就实例化一个单例,JVM保证线程安全
简单实用
唯一缺点:不管用到与否,类装载时就完成实例化
 */

public class Mgr01 {
    private static final Mgr01 INSTANCE = new Mgr01();//类加载时(static)就进行对象实例化(final),是唯一的

    private Mgr01(){};//私有构造函数,禁止外部访问

    // 被static关键字修饰的变量和方法统一属于类的静态资源,是类实例之间共享的。被static关键字修饰的变量、方法属于类变量、类方法,
    // 可以通过【类名.变量名】、【类名.方法名】直接引用,而不需要派生一个类实例出来。
    public static Mgr01 getInstance(){return INSTANCE;}

    public void m(){System.out.println("m");}//主方法

    // 测试一把,是否单例
    public static void main(String args[]){
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1 == m2);
    }
}

1.1.2 写法2

用static语句块来做初始化

package com.DesignPattern.singleton;

/*
饿汉式
 */

public class Mgr02 {
    private static final Mgr02 INSTANCE;
    static {
        INSTANCE = new Mgr02();
    }

    private Mgr02() {
    }//私有构造函数,禁止外部访问

    public static Mgr02 getInstance() {
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }//主方法

    // 测试一把,是否单例
    public static void main(String args[]) {
        Mgr02 m1 = Mgr02.getInstance();
        Mgr02 m2 = Mgr02.getInstance();
        System.out.println(m1 == m2);
    }
}

1.2 懒汉式

在使用时再进行类的实例化。

1.2.1 写法1

使用if语句避免多次实例化,缺点是多线程访问时无法保证唯一性。

package com.DesignPattern.singleton;

/*
懒汉式
非线程安全
 */

public class Mgr03 {
    private static Mgr03 INSTANCE;

    private Mgr03() {
    }//私有构造函数,禁止外部访问

    public static Mgr03 getInstance() {
        if (INSTANCE == null) {
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }//主方法

    // 测试一把,是否单例
    public static void main(String args[]) {
        for(int i=0;i<100;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Mgr03.getInstance().hashCode());
                }
            }).start();
        }
    }
}

1.2.2 写法2

加锁保证线程安全,缺点是给整个获取实例的语句块加锁导致效率降低。

package com.DesignPattern.singleton;

/*
懒汉式
加锁保证线程安全
但整个语句块加锁导致效率降低
 */

public class Mgr04 {
    private static Mgr04 INSTANCE;

    private Mgr04() {
    }//私有构造函数,禁止外部访问

    public static synchronized Mgr04 getInstance() {
        if (INSTANCE == null) {
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Mgr04();
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }//主方法

    // 测试一把,是否单例
    public static void main(String args[]) {
        for(int i=0;i<100;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Mgr04.getInstance().hashCode());
                }
            }).start();
        }
    }
}

1.2.3 写法3

试图减小同步代码块以提高效率,下面这种写法不可行。不可行的原因是,if语句执行过的线程,都会进入执行同步语句块。if语句判断是否存在实例没有加锁,给执行语句块加锁没有意义。
所以这种写法是线程不安全的,加锁没有意义。

package com.DesignPattern.singleton;

/*
懒汉式
试图减小同步代码块以提高效率,这种写法不可行
 */

public class Mgr05 {
    private static Mgr05 INSTANCE;

    private Mgr05() {
    }//私有构造函数,禁止外部访问

    public static Mgr05 getInstance() {
        if (INSTANCE == null) {
            //试图通过减小同步代码块的方式提高效率,这种写法不可行
            synchronized (Mgr05.class){
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Mgr05();
            }
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }//主方法

    // 测试一把,是否单例
    public static void main(String args[]) {
        for(int i=0;i<100;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Mgr05.getInstance().hashCode());
                }
            }).start();
        }
    }
}

1.2.4 写法4

双重检查单例写法,懒汉式。是上一种写法的改良,相比写法2提升了效率,因为不是每个线程都需要加锁了,提高了并发。
在类被一个线程实例化之后,尚未进入第一层if的线程不会执行同步块语句;已经进入同步块之内的线程,执行完第二层if就会归还锁。

注意:为了多线程创建和访问的安全性,需要加volatile关键字。

package com.DesignPattern.singleton;

/*
懒汉式
同步代码块+双判断
线程安全且高效
 */

public class Mgr06 {
    private static volatile Mgr06 INSTANCE;

    private Mgr06() {
    }//私有构造函数,禁止外部访问

    public static Mgr06 getInstance() {
        //第一层用于提高效率,一旦类已被实例化,就不再往下执行
        if (INSTANCE == null) {
            synchronized (Mgr06.class){
                //第二层用于保证单例,防止进入第一层if的线程,获取锁之后再次实例化类
                if(INSTANCE==null){
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }//主方法

    // 测试一把,是否单例
    public static void main(String args[]) {
        for(int i=0;i<100;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Mgr06.getInstance().hashCode());
                }
            }).start();
        }
    }
}

1.3 静态内部类

1.3.1 写法1

在类的内部定义静态内部类,在该静态内部类中定义初始化的外部类的静态实例。
在获取外部类的实例的时候,返回静态内部类定义的经过初始化的外部类的静态实例。
由于加载外部类时不会加载内部类,这样可以实现懒加载。
JVM保证单例,因为JVM在加载类的时候只会加载一次。

package com.DesignPattern.singleton;

/*
静态内部类方式
JVM保证单例
由于加载外部类时不会加载内部类,这样可以实现懒加载
 */

public class Mgr07 {
    private Mgr07(){};//私有构造函数,禁止外部访问

    private static class Mgr07Holder{
        private static final Mgr07 INSTANCE = new Mgr07();
    }

    public static Mgr07 getInstance(){
        return Mgr07Holder.INSTANCE;
    }

    public void m(){System.out.println("m");}//主方法

    // 测试一把,是否单例
    public static void main(String args[]){
        for(int i=0;i<100;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Mgr06.getInstance().hashCode());
                }
            }).start();
        }
    }
}

1.4 枚举实现

1.4.1 写法1

既能保证线程安全,也可以防止反序列化。(没有深究,这种写法没太理解)

package com.DesignPattern.singleton;

public enum  Mgr08 {
    INSTANCE;

    public void m(){}

    // 测试一把,是否单例
    public static void main(String args[]){
        for(int i=0;i<100;i++){
            new Thread(() -> System.out.println(Mgr08.INSTANCE.hashCode())).start();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值