单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
单例模式的优点:
单例模式的缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
优点:懒加载启动快,资源占用小,使用时才实例化,无锁。
缺点:非线程安全。
/**
* @program: server-java-demo
* @description: 单例模式:懒汉,线程不安全
* @author: 亮子说编程
* @create: 2020-10-15 09:06
**/
public class Singleton01 {
private static Singleton01 instance;
private Singleton01() {
}
public static Singleton01 getInstance() {
if (instance == null) {
instance = new Singleton01();
}
return instance;
}
}
这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。
优点:同上,但加锁了。
缺点:synchronized 为独占排他锁,并发性能差。即使在创建成功以后,获取实例仍然是串行化操作。
package com.shenmazong;
/**
* @program: server-java-demo
* @description: 单例模式:懒汉式,线程安全
* @author: 亮子说编程
* @create: 2020-10-15 09:16
**/
public class Singleton02 {
private static Singleton02 instance;
private Singleton02() {
}
// 加锁
public static synchronized Singleton02 getInstance() {
if (instance == null) {
instance = new Singleton02();
}
return instance;
}
}
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。
优点:饿汉模式天生是线程安全的,使用时没有延迟。
缺点:启动时即创建实例,启动慢,有可能造成资源浪费。
package com.shenmazong;
/**
* @program: server-java-demo
* @description: 单例模式:饿汉一
* @author: 亮子说编程
* @create: 2020-10-15 09:24
**/
public class Singleton03 {
private static Singleton03 instance = new Singleton03();
private Singleton03() {
}
public static Singleton03 getInstance() {
return instance;
}
}
这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
优点:懒加载,线程安全。
注:实例必须有 volatile 关键字修饰,其保证初始化完全。
package com.shenmazong.core;
/**
* @program: server-java-demo
* @description: Singleton05
* @author: 亮子说编程
* @create: 2020-10-15 11:23
**/
public class Singleton05 {
// 对保存实例的变量添加volatile的修饰
private volatile static Singleton05 instance = null;
private Singleton05(){
}
public static Singleton05 getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(instance == null){
//同步块,线程安全的创建实例
synchronized(Singleton05.class){
//再次检查实例是否存在,如果不存在才真的创建实例
if(instance == null){
instance = new Singleton05();
}
}
}
return instance;
}
}
优点:将懒加载和线程安全完美结合的一种方式(无锁)。(推荐)
package com.shenmazong.core;
/**
* @program: server-java-demo
* @description: 单例模式:Holder模式(静态内部类)
* @author: 亮子说编程
* @create: 2020-10-15 09:31
**/
public class Singleton04 {
/**
* @Description:
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
* @Param:
* @return:
* @Author: 亮子说编程
* @Date: 2020/10/15 11:19
*/
private static class SingletonHolder{
// 静态初始化器,由JVM来保证线程安全
private static Singleton04 instance = new Singleton04();
}
/**
* @Description: 私有化构造方法
* @Param: []
* @return:
* @Author: 亮子说编程
* @Date: 2020/10/15 11:18
*/
private Singleton04(){
}
public static Singleton04 getInstance(){
return SingletonHolder.instance;
}
}
有两个问题需要注意:
1)、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类 装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2)、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
private static Class getClass(String classname)
throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null) {
classLoader = Singleton01.class.getClassLoader();
}
return (classLoader.loadClass(classname));
}
public class Singleton06 implements java.io.Serializable {
public static Singleton06 INSTANCE = new Singleton06();
protected Singleton06() {
}
private Object readResolve() {
return INSTANCE;
}
}