目录
单例模式
单例模式的定义
-
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个获取该对象实例的方法。
-
目的:为了节省内存资源,保证数据内容的一致性
单例模式的实现步骤
-
构造方法私有化(防止外部类创建该类的对象)
-
类的内部创建一个静态私有对象实例
-
向外提供一个静态的共有函数用于获取该静态私有实例
同步和异步的概念
-
同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其它没有权限的线程需要进行排队。坏处就是效率降低,但是保证了线程安全。
-
异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。坏处就是有安全隐患,但是效率要高一些。
饿汉式-线程安全
-
原理:依赖JVM类加载机制,保证单例(一个对象实例)只被创建一次
-
优点:线程安全、初始化速度快、占用内存小
-
缺点:可能会造成内存浪费--------如果从始至终从未使用过这个实例,则会造成内存的浪费。
public class Singleton1 {
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 {
//构造器私有化,防止外部new
private Singleton() {
}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
returninstance;
}
}
静态代码块饿汉式(和上面方式类似,优缺点上同)
public class Singleton1 {
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 {
//构造器私有化,防止外部new
private Singleton() {
}
private static Singleton instance;
//静态代码块,最先执行,且只执行一次
static {
instance = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
懒汉式-线程不安全
-
原理:类加载时,先不自动创建单例;需要时才创建单例
-
优点:按需加载单例;节约内存
-
缺点:多线程下不安全
这个实现在多线程环境下是不安全的,如果多个线程能够同时进入
if (instance == null)
,并且此时 instance 为 null,那么会有多个线程执行instance = new Singleton();
语句,这将导致多次实例化 instance。
public class Singleton1 {
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 {
//构造器私有化,防止外部new
private Singleton() {
}
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式-同步锁-线程安全
-
原理:只需要对 getInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了多次实例化 instance 的问题。
-
优点:线程安全;节约内存
-
缺点:效率低------当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,因此性能上有一定的损耗。
public class Singleton1 {
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 {
//构造器私有化,防止外部new
private Singleton() {
}
private static Singleton instance;
//synchronized同步锁
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双重校验锁-线程安全(推荐使用)
-
原理:
-
校验锁1:若单例已创建,则直接返回已创建的单例,无需再执行加锁操作
-
校验锁2:防止多次创建单例问题
-
-
优点:线程安全;节省资源(不需过多的同步开销,同步锁=耗时、耗能);效率高
-
缺点:实现复杂(多种判断,易出错)
volatile关键字:
instance 采用 volatile 关键字修饰也是很有必要的。instance = new Singleton();
这段代码其实是分为三步执行:
-
分配内存空间
-
初始化对象
-
将 instance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,有可能执行顺序变为了 1>3>2,这在单线程情况下自然是没有问题。但如果是多线程下,有可能获得是一个还没有被初始化的实例,以致于程序出错。使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
public class Singleton1 {
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 {
//构造器私有化,防止外部new
private Singleton() {
}
//volatile保证可见性,防止指令重排
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
静态内部类(推荐使用)
-
原理:
-
按需加载:在静态内部类里创建单例,在装载该内部类时才会去创建单例
-
线程安全:类是由JVM加载,而JVM只会加载一遍,保证只有一个单例
-
-
优点:线程安全、节省资源(不需过多的同步开销)、实现简单
-
缺点:没有缺点
public class Singleton1 {
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 {
//构造器私有化,防止外部new
private Singleton() {
}
private static class SingletonInstance {
private static final Singleton INSTENCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTENCE;
}
}
枚举实现(最佳推荐使用)
这是单例模式的最佳实践,它实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止被实例化多次。
-
原理:
-
枚举类型 = 不可被继承的类(final)
-
每个枚举元素 = 类静态变量 = 依赖JVM类加载机制,保证单例只被创建一次
-
枚举元素 都通过静态代码块来进行初始化
-
构造方法访问权限默认 = 私有(private)
-
大部分方法都是final
-
-
优点:线程安全、自由序列化、实现更加简单、简洁
-
缺点:没有缺点
public class Singleton1 {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1 == instance2);
System.out.println("instance1.hashCode()="+instance1.hashCode());
System.out.println("instance2.hashCode()="+instance2.hashCode());
instance1.sayOk();
}
}
//使用枚举实现单例
enum Singleton {
INSTANCE; //属性
public void sayOk() {
System.out.println("ok");
}
}