单例模式

单例模式

什么是单例模式

比如一个团队多个开发者都需要操作日志文件,如果每个人都实例化一个日志对象,会导致不必要的开销。为了解决这类问题,单例模式产生了。单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。Windows系统中的回收站就是典型的一种单例模式应用,多次打开回收站不会创建新的窗口。

单例模式的优缺点

优点:
  • 节省内存空间。
  • 避免了频繁的创建销毁对象,可以提高性能。
  • 避免对共享资源的多重占用,简化访问。
  • 为整个系统提供一个全局访问点。
缺点:
  • 不适于变化频繁的独享。
  • 为了节省资源将数据连接池对象设计为单例类,可能会导致共享连接池对象太多,出现连接池溢出。
  • 如果实例化的对象长时间不被使用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失。

单例模式的实现

1、饿汉式:类加载到内存后,就实例化一个单例,JVM保证线程安全

package SingletonPattern;

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

    private Singleton01(){};

    public static Singleton01 getInstance(){return INSTANCE;}

    public void method(){
        System.out.println("Happy Everyday");
    }

    public static void main(String[] args) {
        Singleton01 m1 = Singleton01.getInstance();
        Singleton01 m2 = Singleton01.getInstance();
        System.out.println(m1 == m2);
    }
}

2、懒汉式:不在类加载的时候初始化,按需初始化

package SingletonPattern;

import org.omg.PortableServer.THREAD_POLICY_ID;

public class Singleton02 {
    //不能定义为final型,因为常量需要初始化
    private static Singleton02 INSTANCE;

    private Singleton02(){

    }

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

    public void method(){
        System.out.println("Happy Everyday");
    }

    public static void main(String[] args) {
        for(int i = 0; i < 10; i++){
//同一类的不同对象hashcode是不同的
            new Thread(()->{
                System.out.println(Singleton02.getInstance().hashCode());
            }).start();
        }
    }
}

结果
1184893857
219573733
449584288
1924898670
103512977
585605895
967740059
1802506717
499531361
1416599099

当多线程操作时,懒汉模式会出现,线程不安全的问题,比如A,B两个线程同时首次创建单例对象,A线程进入判空条件,创建对象之前,B线程此时INSTANCE依然为空,此时A,B线程会创建两个对象。

3、在懒汉式上添加同步方法:解决懒汉模式线程不安全的问题

package SingletonPattern;

import org.omg.PortableServer.THREAD_POLICY_ID;

public class Singleton03 {
    //不能定义为final型,因为常量需要初始化
    private static Singleton03 INSTANCE;

    private Singleton03(){

    }

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

    public void method(){
        System.out.println("Happy Everyday");
    }

    public static void main(String[] args) {
        for(int i = 0; i < 10; i++){
            new Thread(()->{
                System.out.println(Singleton03.getInstance().hashCode());
            }).start();
        }
    }
}
结果
1416599099
1416599099
1416599099
1416599099
1416599099
1416599099
1416599099
1416599099
1416599099
1416599099

4、双重检查法:虽然通过synchronized解决了线程不安全的问题,但是也带了效率的下降。在同步块中添加判空条件,否则A,B线程拿到锁后都会创建一个对象。

package SingletonPattern;

import org.omg.PortableServer.THREAD_POLICY_ID;

public class Singleton04 {
    //不能定义为final型,因为常量需要初始化
    private static volatile Singleton04 INSTANCE;

    private Singleton04(){

    }

    public static synchronized Singleton04 getInstance(){
        if(INSTANCE == null){
            synchronized (Singleton04.class) {
                if(INSTANCE == null) {
                    INSTANCE = new Singleton04();
                }
            }
        }
        return INSTANCE;
    }

    public void method(){
        System.out.println("Happy Everyday");
    }

    public static void main(String[] args) {
        for(int i = 0; i < 10; i++){
            new Thread(()->{
                System.out.println(Singleton04.getInstance().hashCode());
            }).start();
        }
    }
}

5、静态内部类方式:静态内部类在类加载的时候不会加载,在调用getInstance时加载,实现了懒加载。

package SingletonPattern;

import sun.applet.AppletResourceLoader;

/**
 * 静态内部类方式
 */
public class Singleton05 {
    private Singleton05(){};

    private static class Singleton05Holder{
        private final static Singleton05 INSTANCE = new Singleton05();
    }

    public static Singleton05 getInstance(){
        return Singleton05Holder.INSTANCE;
    }

    public void method(){
        System.out.println("Happy Everyday");
    }

    public static void main(String[] args) {
        for(int i = 0; i < 10; i++){
            new Thread(()->{
                System.out.println(Singleton05.getInstance().hashCode());
            }).start();
        }
    }
}

6、枚举实现:枚举类是一个抽象类,没有构造方法,只能有一个实例,不仅可以解决线程同步,还可以防止反序列化。

package SingletonPattern;

public enum Singleton06 {
    INSTANCE;

    public void method(){
        System.out.println("Happy Everyday");
    }

    public static void main(String[] args) {
        for(int i = 0; i < 10; i++){
            new Thread(()->{
                System.out.println(Singleton05.getInstance().hashCode());
            }).start();
        }
    }
}
原文地址:https://www.cnblogs.com/happysml/p/13849959.html