单例模式简介
单例模式就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
- 单例模式保证了系统的内存中该类只有一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当实例化一个单例类的时候,要使用相应的getInstance方法而不是new一个新的
单例模式的总体步骤:
- 构造器私有化
- 类的内部创建对象
- 向外暴露一个静态的公共方法
- 代码实现
单例模式的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~");
}
}
- 避免了多线程同步问题,还可以防止反序列化重新创建新的对象