Java设计模式---单例模式学习笔记

单例模式简介

单例模式就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
  1. 单例模式保证了系统的内存中该类只有一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  2. 当实例化一个单例类的时候,要使用相应的getInstance方法而不是new一个新的

单例模式的总体步骤:

  1. 构造器私有化
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法
  4. 代码实现

单例模式的8种方式

  • 饿汉式
    • 饿汉式(静态常量)
    • 饿汉式(静态代码块)
  • 懒汉式
    • 懒汉式(线程不安全)
    • 懒汉式(线程安全,同步方法)
    • 懒汉式(线程安全,同步代码块)
  • 双重检查
  • 静态内部类
  • 枚举

饿汉式(静态常量)

//饿汉式(静态变量)
public class SingletonTest01 {
    public static void main(String[] args) {
        //测试
        SingleTon instance1 = SingleTon.getInstance();
        SingleTon instance2 = SingleTon.getInstance();
        //其实拿到的是同一个实例对象
        System.out.println(instance1==instance2);
        //比较哈希码
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}
class SingleTon {
    //1.构造器私有化
    private SingleTon() {

    }
    //2.在本类内部创建对象实例
    private final static SingleTon instance = new SingleTon();
    //3.对外提供一个公有的静态方法,返回实例对象
    public static SingleTon getInstance() {
        return instance;
    }
}

优点和缺点

  • 优点:应为是静态的方法和变量,所以在类装载的时候就完成了实例化,避免了线程同步问题.
  • 缺点:在类装在的时候就完成实例化,没有达到Lazy Loading的效果.如果从始至终都没有使用这个实例,就会造成空间的浪费
  • 可能造成内存浪费

饿汉式(静态代码块)

与静态变量的写法类似,只不过在静态代码块中创建了新的单例对象

//饿汉式(静态代码块)
public class SingletonTest02 {
    public static void main(String[] args) {
        //测试
        SingleTon instance1 = SingleTon.getInstance();
        SingleTon instance2 = SingleTon.getInstance();
        //其实拿到的是同一个实例对象
        System.out.println(instance1==instance2);
        //比较哈希码
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}

class SingleTon {
    //1.构造器私有化
    private SingleTon() {}
    //2.在本类内部创建对象实例
    private static SingleTon instance;
    
    static {
        //在静态代码块中创建单例对象
        instance = new SingleTon();
    }
    //3.对外提供一个公有的静态方法,返回实例对象
    public static SingleTon getInstance() {
        return instance;
    }
}

优点和缺点

  • 优点:应为是静态的方法和变量,所以在类装载的时候就完成了实例化,避免了线程同步问题.
  • 缺点:在类装在的时候就完成实例化,没有达到Lazy Loading的效果.如果从始至终都没有使用这个实例,就会造成空间的浪费
  • 可能造成内存浪费
  • 优缺点与静态变量的写法类似

懒汉式(线程不安全)

  • 解决了性能的问题,只有用到了实例,实例才会被创建
  • 其实是在getInstance中加入了一个判断,如果实例为null则创建,否则直接返回实例
  • 但是在多线程中可能会出错
public class SingletonTest03 {
    public static void main(String[] args) {
        System.out.println("懒汉式,线程不安全");
        //测试
        SingleTon instance1 = SingleTon.getInstance();
        SingleTon instance2 = SingleTon.getInstance();
        //其实拿到的是同一个实例对象
        System.out.println(instance1==instance2);
        //比较哈希码
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}

class SingleTon {
    private SingleTon () {}    //构造私有化
    private static SingleTon instance;    //内部创建对象
    //提供一个静态的公有方法,当使用到该方法的时候才创建intance
    //即懒汉式
    public static SingleTon getInstance() {
        if(instance==null) {    //就是在这里加了一个判断,但是如果最开始两个或多个线程同时进入到这里,实例还没创建好,可能判断结果都为真,就会创建多个实例
            instance = new SingleTon();
        }
        return instance;
    }
}

优点和缺点

  • 优点:起到了Lazy Loading的效果
  • 缺点:在多线程下,一个线程进入了if判断,还没来得及执行,另一个线程也进入了这个判断,这个时候就会产生多个实例但是只能在单线程下使用
  • 在实际开发中,不要使用这种方式

懒汉式(线程安全,同步方法)

其实就是在线程不安全的基础上加了synchronized关键字
public class SingletonTest04 {
    public static void main(String[] args) {
        System.out.println("懒汉式(2),线程安全");
        //测试
        SingleTon instance1 = SingleTon.getInstance();
        SingleTon instance2 = SingleTon.getInstance();
        //其实拿到的是同一个实例对象
        System.out.println(instance1==instance2);
        //比较哈希码
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}

class SingleTon {
    private static SingleTon instance;
    private SingleTon () {}
    //提供一个静态的公有方法 加入了同步处理的代码,解决的了线程安全问题
    //即懒汉狮
    public static synchronized SingleTon getInstance() {
        if(instance==null) {
            instance = new SingleTon();
        }
        return instance;
    }
}

优点和缺点

  • 优点:解决了线程安全问题
  • 缺点:效率很低, 程序运行的过程中, getInstance()方法肯定要被执行很多次,并且都要进行同步
  • 其实只要执行一次实例化,后面的想要获得实例,直接return
  • 在实际开发中,不推荐使用这种方式

双重检查

  • 有两次检查是否创建了实例
  • 前面一个是判断是否已经创建了对象
  • 后面一个是用了同步的方式,保证只有一个线程创建实例
public class SingletonTest06 {
    public static void main(String[] args) {
        System.out.println("双重检查");
        //测试
        SingleTon instance1 = SingleTon.getInstance();
        SingleTon instance2 = SingleTon.getInstance();
        //其实拿到的是同一个实例对象
        System.out.println(instance1==instance2);
        //比较哈希码
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}

class SingleTon {
    private static volatile SingleTon instance;    //保持内存可见,防止指令重排(一个实例创建好之后其余的线程可以立马知道)
    private SingleTon () {}
    //提供一个静态的公有方法 加入双重检查的代码,解决的了线程安全问题,同时解决懒加载问题
    public static SingleTon getInstance() {
        if(instance==null) {
            synchronized (SingleTon.class) {    //同步,每次只有一个线程进去
                if(instance==null) {
                    instance = new SingleTon();
                }
            }
        }
        return instance;
    }
}

优点和缺点

  • 实例化代码只执行了一次,后面再次访问的时候直接return实例化对象,避免了反复进行方法同步
  • 线程安全;延迟加载;效率较高
  • 推荐使用

静态内部类

  • 初始化实例的时候只有一个线程
  • 装载时不会实例化,调用了getInstance方法时才会实例化
public class SingletonTest07 {
    public static void main(String[] args) {
        System.out.println("静态内部类完成");
        //测试
        SingleTon instance1 = SingleTon.getInstance();
        SingleTon instance2 = SingleTon.getInstance();
        //其实拿到的是同一个实例对象
        System.out.println(instance1==instance2);
        //比较哈希码
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}
//静态内部类 推荐使用
class SingleTon {
    private static SingleTon instance;
    //构造器私有化
    private SingleTon () {}
    //写一个静态内部类,该类中有一个静态的属性SingleTon
    private static class SingleTonInstance {
        //装载是安全的
        private static final SingleTon INSTANCE = new SingleTon();
    }

    //提供一个静态的公有方法,直接返回SingleTonInstance的成员变量
    public static SingleTon getInstance() {
        return SingleTonInstance.INSTANCE;
    }
}

优点和缺点

  • 类装载的机制保证了初始化的时候只有一个线程
  • 在SingleTon 被装载的时候不会立即实例化,而是在需要实例化时,调用getInstance才会装载,从而完成实例化

枚举

  • 最简单的写法
public class SingleTonTest08 {
    public static void main(String[] args) {
        SingleTon instance1 = SingleTon.INSTANCE;
        SingleTon instance2 = SingleTon.INSTANCE;
        System.out.println(instance1==instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
        instance1.sayOK();
    }
}
//使用枚举可以使用单例
enum SingleTon {
    INSTANCE;   //属性,保证是一个单例
    public void sayOK() {
        System.out.println("OK~");
    }
}
  • 避免了多线程同步问题,还可以防止反序列化重新创建新的对象
posted @ 2020-05-05 08:58  退役的熬夜选手  阅读(182)  评论(0编辑  收藏  举报