单例模式:
单例模式(Singleton Pattern)是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
结构:
单例设计模式主要有两种角色,它们分别是:
- 单例类:该类只能创建一个对象
- 访问类:该类其实就是测试类,即使用单例类
单例设计模式可分为如下两类:
- 饿汉式:类加载就会导致该单实例对象被创建。也就是说类加载的时候,该类的对象就创建好了
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
所以,我们如何去区分饿汉式和懒汉式呢?只需要去判断该单实例对象是否是在类加载的时候创建即可,若是,则是饿汉式,若不是,则是懒汉式。
饿汉模式:
创建该类:
- 私有构造方法:为什么要私有构造方法呢?因为私有了构造方法之后,外界就访问不到这个构造方法了,访问不到的话外界就无法去创建对象了
- 在本类中创建一个本类对象供外界去使用
- 提供一个公共的访问方式,让外界获取该对象
/**
* 饿汉模式
*/
public class Singleton {
//1.私有构造方法
private Singleton() {}
//在本类中创建本类对象
private static Singleton instance = new Singleton();
//3.提供一个公共的访问方式,让外界获取改对象
public static Singleton getInstance(){
return instance;
}
}
注意,以上该类中对外提供的公共访问方法,除了使用public修饰之外,还使用了static修饰,这是为什么呢?因为外界无法创建Singleton类的对象,既然不能创建对象的话,那么就无法去调用其非静态方法了,所以这里面我们对外要提供的是静态方法。而且,由于静态的不能直接访问非静态的,所以instance成员变量还得使用static来修饰。
创建一个测试类:
/**
* 饿汉模式,测试类
*/
public class Client {
public static void main(String[] args) {
//创建Singleton类对象
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
//判断获取到的两个是否是同一个对象
System.out.println(instance1 == instance2);
}
}
可以看到打印结果是true,也就是说两次获取到Singleton类的对象是同一个对象,这样,我们就已经保证Singleton这个类只能创建一个对象了。
饿汉模式——枚举方式
枚举类实现单例模式是极力推荐的单例实现方式,因为枚举类型是线程安全的(也即它不会存在线程安全问题),并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式;其二,枚举的写法非常简单,而且枚举类型是所有单例实现中唯一一种不会被破坏的单例实现方式。
代码实现:
/**
* 枚举实现方式
*/
public enum Singleton {
INSTANCE;
}
枚举方式的实现就是这么简单
测试类:
public class Client {
public static void main(String[] args) {
Singleton singleton1 = Singleton.INSTANCE;
Singleton singleton2 = Singleton.INSTANCE;
System.out.println(singleton1 == singleton2);
}
}
可以看到打印结果是true,这说明多次获取到的就是同一个对象。
如果不考虑浪费内存空间的话,那么推荐首选枚举这种实现方式。
懒汉模式:
懒汉式它也有多种实现方式,分为线程安全的和线程不安全的
实现一:线程不安全的
创建该类:
- 私有构造方法
- 在成员位置声明一个该类的成员变量,不过不要给其赋值
- 对外提供一个公共的访问方式
/**
* 懒汉模式
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//声名Singleton类型的变量instance
private static Singleton instance;//只是声名了该类型的变量,并没有对其赋值
//对外提供访问方式
public static Singleton getInstance(){
// 判断instance是否为null,如果为null,那么说明还没有创建Singleton类的对象
// 如果没有创建的话,那么我们就创建一个并返回;如果有创建,那么直接返回即可
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
测试类:
public class Client {
public static void main(String[] args) {
//创建Singleton类对象
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
//判断获取到的两个是否是同一个对象
System.out.println(instance1 == instance2);
}
}
打印结果是true,这说明咱们多次获取到的是同一个对象。
你觉得以上Singleton类写的有没有什么问题啊?肯定是存在问题的,如果是多线程环境下,那么就会出现线程安全问题。
为什么这么说呢?假设现在是多线程环境,两个线程同时调用getInstance方法,线程1拿到cpu的执行权,它在调用getInstance方法时,首先肯定是要做一个判断的,做完判断之后,它会进入到判断里面。此时,如果线程2拿到了cpu的执行权,那么线程1就会处于等待状态,同样,线程2在调用getInstance方法时也是要进行判断的,你觉得线程2还能进入到判断里面吗?
肯定能够啊!因为此时if判断条件是成立的,即instance是等于null的,这一切都是由于线程1还处于等待状态,还未执行下面的代码而导致的。也就是说,只要线程2获取到了cpu的执行权,那么它也会进入到判断里面。这样,以上Singleton类创建的就不是单个对象了,而是多个。
实现二:线程安全的
很简单,只须在以上Singleton类的getInstance方法上加上一个同步关键字(即synchronized)即可。
/**
* 懒汉模式
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//声名Singleton类型的变量instance
private static Singleton instance;//只是声名了该类型的变量,并没有对其赋值
//对外提供访问方式
public static synchronized Singleton getInstance(){
// 判断instance是否为null,如果为null,那么说明还没有创建Singleton类的对象
// 如果没有创建的话,那么我们就创建一个并返回;如果有创建,那么直接返回即可
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
实现三:双重检查锁
创建该类:
- 私有构造方法
- 在成员位置声明一个该类的成员变量,不过不要给其赋值
- 对外提供一个公共的访问方式,在该方法里面记得要做两次判断
/**
* 双重检查锁方式
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//声名Singleton类型的变量instance
private static Singleton instance;
// 对外提供公共的访问方式
public static Singleton getInstance() {
// 第一次判断,如果instance的值不为null,那么就不需要抢占锁了,直接返回对象即可
if (instance == null) {
synchronized (Singleton.class) {
// 第二次判断
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
对于getInstance方法来说,我们是做了两次判断:
- 第一次判断:判断instance是否等于null,若是,则需抢占锁;若不是,则直接返回instance即可,由于并没有去持有锁,所以效率就可以得到提升了
- 第二次判断:如果第一次判断时,instance等于null,那么进来判断里面之后我们得先持有一把锁,所以在这儿咱们就编写了一个同步代码块,而且锁对象就是当前类的字节码对象,这样,我们就可以在同步代码块里面做第二次判断了。所做判断很简单,无非就是判断instance是否为null,若是,则需创建一个本类对象并将其赋值给instance;若不是,则直接返回instance即可
实现四:静态内部类
创建该类:
- 私有构造方法
- 定义一个静态内部类,并在内部类中声明并初始化外部类的对象
- 在外部类里面,对外提供一个公共的访问方式,供外界去获取本类对象
/**
* 静态内部类方式
*/
public class Singleton {
// 私有构造方法
private Singleton() {}
// 定义一个静态内部类
private static class SingletonHolder {
// 在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton(); // 为了防止外界对该静态属性进行修改,故在其上加了一个final关键字,也即意味着它就是一个常量了
}
// 提供公共的访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。