Java单例模式8种方式 详解

Java单例模式8种方式 详解Singleton所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例。运用场景很多,例如网站的在线人数,window系统的任务管理器,网站计数器等等,这些都是单例模式的运用。单例模式有常见的8种形式,如下:1.Lazy1【不可用】懒汉式1:线程不稳定延迟初始化多线程不安全是最基本的实现方式,不支持多线程,因为没有synchronized加锁,多线程不能工作。实现图多线程则会出现,当Singleton_La

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

Singleton

1、单例模式(Singleton Pattern):确保某一个类最多只有一个实例,并向整个系统提供这个实例,即该类需提供一个访问唯一实例的全局方法,这个类称为单例类。单例模式的目的是使得某个类最多只有一个实例。

2、为了确保单例类最多只有一个实例,且能够向外部提供唯一实例,单例类应具备以下特点特征:(1)构造方法私有化;(2)能够生成唯一实例;(3)存在能够向外部提供唯一实例的方法;(4)实例和方法需用static关键词修饰。

3、单例模式确保了系统中只能存在唯一实例,则在内存里只有一个实例,这样在频繁的创建和销毁实例时可以减少内存的开销。但由于单例类只向外部提供了访问实例的方法、没有接口,无法被重用和扩展。

单例设计模式主要解决的是类的频繁创建与销毁问题,通过控制类实例的创建来节省系统资源。

运用场景很多,例如网站的在线人数,window系统的任务管理器,网站计数器等等,这些都是单例模式的运用。单例模式有常见的8种形式,如下:

1.Lazy1【不可用】

public class Singleton_Lazy1 { 
   
	private Singleton_Lazy1() { 
   
	};

	private static Singleton_Lazy1 instance = null;

	public static Singleton_Lazy1 getInstance() { 
   
		if (instance == null) { 
   
			instance = new Singleton_Lazy1();
		}
		return instance;
	}
}
  • 懒汉式1:

    • 线程不稳定

    • 延迟初始化

    • 多线程不安全

    • 是最基本的实现方式,不支持多线程,因为没有synchronized加锁,多线程不能工作。

    • 实现图

    图一

    • 多线程则会出现,当Singleton_Lazy1类刚刚被初始化,instance对象还是空,这时候两个线程同时访问到getInstance方法,因为Instance是空,所以A\B两个线程都通过了instance为空的判断,则A\B两个线程都会实例化对象,单例失败。

      不加同步的懒汉式是线程不安全的,如下示例:

在这里插入图片描述

2.Lazy2(同步方法)【不建议使用】

public class Singleton_Lazy2 { 
   
	private Singleton_Lazy2() { 
   
	};

	private static Singleton_Lazy2 instance = null;

	public static synchronized Singleton_Lazy2 getInstance() { 
   
		if (instance == null) { 
   
			instance = new Singleton_Lazy2();
		}
		return instance;
	}
}
  • 懒汉式2:

    • 线程稳定

    • 延迟初始化

    • 多线程安全

    • 优点:调用才初始化,避免内存浪费。

    • 缺点:必须加锁synchronized才能保证单例,但每次调用都要加锁会影响效率。

    • 实现图
      在这里插入图片描述

    • 利用synchronized关键字对getInstance方法加锁使得多线程安全且稳定但效率不高。

      多线程实现方法,如下图所示:

    在这里插入图片描述

3.DCL1(同步代码块)【不可用】

public class Singleton_DCL1 { 
   
	private Singleton_DCL1() { 
   
	};

	private static Singleton_DCL1 singleton;

	public static Singleton_DCL1 getInstance() { 
   
		if (singleton == null) { 
   
			synchronized (Singleton_DCL1.class) { 
   
                if (singleton == null) { 
   
					singleton = new Singleton_DCL1();
			    }
		    }
		    return singleton;
	    }
    }
}
  • 双重检测机制:

    • 延迟初始化

    • 多线程不安全

    • 线程稳定

    • 1.为了防止new Singleton被执行多次,因此在new操作之前加上Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。

    • 2.进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象。这种同步并不能起到线程同步的作用

    • 但是这样的双重检测机制仍然不是绝对线程安全!这里涉及到JVM编译器的指令重排。

      一般创建一个对象的时候会有三个步骤:

      instance = new Singleton
      Value =allocate();    //1:分配对象的内存空间 
      ctorInstance(Value);  //2:初始化对象 
      instance =Value;     //3:设置instance指向刚分配的内存地址 
      

      但这三步并不是固定不变的,有可能会经过JVM和CPU的优化,指令将会重排为:

      instance = new Singleton
      Value =allocate();  //1:分配对象的内存空间 
      instance =Value;   //3:设置instance指向刚分配的内存地址 
      ctorInstance(Value); //2:初始化对象 
      

      当线程A执行完1、3时,instance对象还未完成初始化,但是已经不指向null。这个时候如果B线程进入CPU,则会抢先执行if(instance == null)的判断结果为false,这个时候getInstance方法则会返回一个没有初始化完成的instance对象,结果如下图:

      image-20210309103405695

4.DCLPro【推荐使用】

public class Singleton_DCLPro { 
   
	private Singleton_DCLPro() { 
   
	};

	private volatile static Singleton_DCLPro singleton;

	public static Singleton_DCLPro getInstance() { 
   
		if (singleton == null) { 
   
			synchronized (Singleton_DCLPro.class) { 
   
				if (singleton == null) { 
   
					singleton = new Singleton_DCLPro();
				}
			}
		}
		return singleton;
	}
}
  • 双检锁/双重校验锁(DCL,即 Double-Checked-Locking):

    • 线程稳定

    • 延迟初始化

    • 多线程安全

    • volatile关键字是防止创建对象时的重排序,在访问volatile变量时不会执行加锁操作。

      volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。

    • 完美解决3.DCL1的问题。

5.Hunger1(静态常量)【可用】

public class Singleton_Hunger1 { 
   
	private final static Singleton_Hunger1 INSTANCE = new Singleton_Hunger1();

	private Singleton_Hunger1() { 
   
	};

	public static Singleton_Hunger1 getInstance() { 
   
		return INSTANCE;
	}
}
  • 饿汉式(静态常量):

    • 线程稳定

    • 不会延迟加载

    • 多线程安全

    • 优点:采用了类装载的机制来保证初始化实例时只有一个线程,避免了线程同步问题。没有用synchronized进行加锁,提高执行效率。

    • 缺点:在类装载的时候就完成实例化,没有达到延迟加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

      image-20210115210718610

6.Hunger2(静态代码块)【可用】

public class Singleton_Hunger2 { 
   
	private static Singleton_Hunger2 instance;
	static { 
   
		instance = new Singleton_Hunger2();
	}

	private Singleton_Hunger2() { 
   
	};

	public Singleton_Hunger2 getInstance() { 
   
		return instance;
	}
}
  • 饿汉式(静态代码块):

    • 线程稳定

    • 不会延迟初始化

    • 多线程安全

    • 优缺点与Hunger1一样。

    • 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。

      static{ 
             
         instance = new Singleton_Hunger2;
      }
      

7.Pattern(静态内部类)【推荐使用】

public class Singleton_Pattern { 
   
	private Singleton_Pattern() { 
   
	};

	private static class SingletonInstance { 
   
		private static final Singleton_Pattern INSTANCE = new Singleton_Pattern();
	}

	public Singleton_Pattern getInstance() { 
   
		return SingletonInstance.INSTANCE;
	}
}
  • 静态内部类:

    • 线程稳定

    • 延迟加载

    • 多线程安全

    • 优点:和5.Hunger1类似,采用了类装载的机制来保证初始化实例时只有一个线程。
      但静态内部类则可以达到延迟加载的效果,在Singleton_Pattern类被装载的时候并不会马上实例化,而是在需要实例化的时候再调用getInstance方法实例化,这样才会装载静态内部类SingletonInstance,从而达到Singleton_Pattern的实例化。 类的静态属性只会在第一次加载类的时候初始化,如此在类初始化的时候其他进程是无法进入的,从而保护了线程的安全。

    • 总结为:避免了线程不安全,延迟加载,效率高。

      在这里插入图片描述

8.Enum【不常用】

public enum Singleton_E { 
   
	INSTANCE;
	public void whateverMethod() { 
   
		System.out.println("这是一个枚举单例");
	}
}

不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。代码简洁。使用起来方便。

关键字

synchronized

synchronized的使用

  • 修饰实例方法,对当前实例对象加锁
  • 修饰静态方法,对当前类的Class对象加锁
  • 修饰代码块,对synchronized括号内的对象加锁
  • 不可锁空对象

volatile

在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

volatile关键字是防止创建对象时的重排序,在访问volatile变量时不会执行加锁操作。

volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/169699.html原文链接:https://javaforall.net

(0)
上一篇 2022年8月11日 上午9:36
下一篇 2022年8月11日 上午9:36


相关推荐

  • js闭包面试题经典_js闭包原理

    js闭包面试题经典_js闭包原理说明最近看到这样一段代码functionfun(n,o){console.log(o);return{fun:function(m){returnfun(m,n);}};}vara=fun(0);a.fun(1);a.fun(2);a.fun(3);varb=f

    2022年8月30日
    7
  • 学会理解并编辑/etc/fstab

    学会理解并编辑/etc/fstabfstab etc fstab 是 Linux 下比较重要的配置文件 它包含了系统在启动时挂载文件系统和存储设备的详细信息 下面是我机子上的 fstab 文件 nbsp LABEL nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp ext3 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp defaults nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp 11 nbsp LABEL boot

    2026年3月17日
    2
  • 收藏级干货!一文吃透大模型智能体:LLM-based Agents核心原理

    收藏级干货!一文吃透大模型智能体:LLM-based Agents核心原理

    2026年3月16日
    3
  • 网站开发团队成员(项目团队)

    1.项目带头人(Boss):通常是项目的发起人,为项目规划企业战略目标,对项目的成败负最终责任。2.项目经理:这个不用说了是项目当然需要PM,建议是通过PMP认证的项目经理,主要负责项目各个过程的管理,以及过程优化降低开发风险。 3.系统架构师:架构师不单单是技术架构,还

    2022年4月10日
    139
  • 使用Servlet遇到的问题Caused by: java.lang.IllegalArgumentException: servlet映射中的<url pattern>[servlet]无效

    使用Servlet遇到的问题Caused by: java.lang.IllegalArgumentException: servlet映射中的<url pattern>[servlet]无效Causedby java lang IllegalArgum servlet 映射中的 urlpattern servlet 无效报这个错是因为 web xml 中的 url 写的路径不对 应该在 servlet 前面加一个 urlpattern

    2025年10月8日
    7
  • Linux系统查看当前Cuda版本

    Linux系统查看当前Cuda版本1 方法一 nvcc V V 要大写 输出的 release10 0 时实际的 cuda 版本 2 方法二 nvidia smi 其中 DriverVersio 是 NVIDIA 驱动版本 CUDAVersion 是指和驱动对应的 CUDA 版本 所以可以根据对应的 cuda 版本可以安装新的 cuda 另外 cuda 是向下兼容的 所以可以安装更高一点的 cuda

    2026年3月26日
    1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号