文章目录
何谓单例模式?
单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
单例模式,简单的说就是 一个类只能有一个实例(只被new一次),并且在整个项目中都能访问到这个实例。
单例模式的优点
1、在内存中只有一个对象,节省内存空间。
2、避免频繁的创建销毁对象,可以提高性能。
3、避免对共享资源的多重占用。
4、可以全局访问。
单例模式实现整体思路流程
首先我们要清楚单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。
单例模式的常规实现思路大致相同为以下三个步骤:
1、私有构造方法
2、指向自己实例的私有静态引用
3、以自己实例为返回值的静态的公有的方法
当然也可以理解为
1、私有化构造方法,让外部不能new。
2、本类内部创建对象实例【静态变量目的是为了类加载的时候创建实例】
3、提供一个公有的static静态方法(一般该方法使用getInstance这个名称),返回实例对象。
将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。
单例模式的适用场景
由于单例模式有很多独特的优点,所以是编程中用的比较多的一种设计模式。我总结了一下我所知道的适合使用单例模式的场景:
1、需要频繁实例化然后销毁的对象。
2、创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3、有状态的工具类对象。
4、频繁访问数据库或文件的对象。
单例模式的八种姿态写法
如果是没有接触单例模式的读者朋友强烈建议你们动手敲一遍,不要复制,不然没效果!
真正轻而易举的理解单例模式,JVM的类加载知识是不能少的,不然你只是会敲的层次,啥?不懂类加载?放心,宜春就是要你会,要你理解透彻。
深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】
姿态一:饿汉式1(静态变量)
package singletonPattern;
//饿汉式(静态变量)
class Singleton{
//1、私有化构造方法,让外部不能new
private Singleton(){
}
//2、本类内部创建对象实例【静态变量目的是为了类加载的时候创建实例】
private final static Singleton instance=new Singleton();
//3、提供一个公有的static静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
//以下是测试代码=====================
public class SingletenDemo1 {
public static void main(String[] args) {
Singleton singleton=Singleton.getInstance();
Singleton singleton2=Singleton.getInstance();
//验证一:
System.out.println(singleton==singleton2);
//验证二:
System.out.println(singleton.hashCode());
System.out.println(singleton2.hashCode());
}
}
//运行结果:
// true
// 460141958
// 460141958
/*
饿汉式(静态变量)方法
优点:写法简单,在类加载的时候就完成了实例化,同时也就避免了线程同步问题,因此线程安全
缺点:由于是在类加载时就完成了实例化,没有达到懒加载的效果。如果一直没有使用过这个实例,就造成了内存的浪费!
总结:这种方式基于ClassLoader类加载机制避免了多线程的同步问题,只不过instance属性在类加载就实例化,在单例模式中大多数都是调用getInstance方法,
由于getInstance方法是static静态的,调用它肯定会触发类加载!但是触发类加载的原因有很多,我们不能保证这个类会通过其他的方式触发类加载(比如调用了其他的static方法)
这个时候初始化instance就没有达到lazy loading 懒加载的效果,可能造成内存的浪费!
饿汉式(静态变量)这种方式可以使用但是会造成内存的浪费!
*/
姿态二:饿汉式2(static静态代码块)
package singletonPattern;
//饿汉式2(static静态代码块)
class Singleton2{
private Singleton2(){
}
private static Singleton2 instance;
static{ //把创建单例对象的操作放进了static静态代码块中==============
instance = new Singleton2();
}
public static Singleton2 getInstance(){
return instance;
}
}
//饿汉式2(static静态代码块)其实和第一种饿汉式(静态变量)方法差不多,其优缺点一致!
//唯一不同的就是把创建单例对象的操作放进了static静态代码块中
姿态三:懒汉式1(线程不安全)
package singletonPattern;
//懒汉式1(线程不安全)
class Singleton3{
private Singleton3(){
}
private static Singleton3 instance;
public static Singleton3 getInstance(){
if(instance == null){
instance=new Singleton3();
}
return instance;
}
}
/*
懒汉式(线程不安全)的这种方式起到了懒加载的效果,但只能在单线程下使用。
如果在多线程下,一个线程进入了if(singleton==null)判断语句块,还没执行产生实例的句子,另一个线程
又进来了,这时会产生多个实例,所以不安全。
结语:懒汉式(线程不安全)在实际开发中,不要使用这种方式!!存在潜在危险
*/
姿态四:懒汉式2(线程安全)
package singletonPattern;
//懒汉式2(线程安全)
class Singleton4{
private Singleton4(){
}
private static Singleton4 instance;
public static synchronized Singleton4 getInstance(){
if(instance == null){
instance=new Singleton4();
}
return instance;
}
}
/*
懒汉式2(线程安全)方式
优点:线程安全
缺点:效率太低,每次调用getInstance方法都要进行同步
结语:懒汉式2(线程安全)方式在开发中不推荐使用,主要是效率太低了*/
姿态五:饿汉式2(static静态代码块)
package singletonPattern;
//懒汉式3 同步代码块(线程安全) 但是不满足单例,在多线程下依旧会有多个实例
class Singleton5{
private Singleton5(){
}
private static Singleton5 instance;
public static Singleton5 getInstance(){
if(instance == null){ //多线程情况下可能多个线程进入这个if块
synchronized (Singleton5.class){ //到这里只会一个一个创建实例,虽然安全,但是就不再是单例了
instance=new Singleton5();
}
}
return instance;
}
}
/*
懒汉式3 同步代码块(线程安全) 但是不满足单例,依旧会有多个实例
结语:懒汉式3 同步代码块(线程安全)方式在开发中不使用 ,实际上这个单例设计的有点搞笑*/
姿态六:双重检查单例
package singletonPattern;
//双重检查应用实例方式
class Singleton6{
private Singleton6(){}
private static volatile Singleton6 singleton;
public static Singleton6 getInstance(){
if(singleton==null){
synchronized(Singleton6.class){
if(singleton == null){
singleton= new Singleton6();
}
}
}
return singleton;
}
}
/*
双重检查应用实例方式:
线程安全、延迟加载、效率较高
结语:开发中推荐使用!
*/
Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile
变量的最新值。这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。
姿态七:静态内部类单例
package singletonPattern;
//static静态内部类单例
class Singleton7{
private Singleton7(){}
private static volatile Singleton7 instance;
//写一个static静态内部类,给该类添加一个static静态instance属性
private static class SingletonInstance{
private static final Singleton7 SINGLETON_7=new Singleton7();
}
//
public static synchronized Singleton7 getInstence(){
return SingletonInstance.SINGLETON_7;
}
}
/*
静态内部类单例方式
1、这种方式采用了类加载机制来保证初始化实例时只有一个线程
2、巧妙的将实例化Singleton操作放进getInstance方法中,getInstance方法返回静态内部类中实例化好的Singleton
3、类的静态属性只会在第一次加载类的时候初始化,也就是只会初始化一次,在这里,JVM帮我们保证了线程的安全,类在初始化时,别的线程无法进入。
优点:线程安全、利用静态内部类特点实现延迟加载、效率高
开发中推荐使用这种静态内部类单例方式!
static静态内部特点:
1、外部类加载不会导致内部类加载,保证了其懒加载
*/
要清楚这个单例,必须要明白static静态内部特点,也就是外部类加载不会导致内部类加载!
姿态八:饿汉式2(static静态代码块)
package singletonPattern;
//使用枚举
import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader;
enum Singleton8{
INSTANCE;
public void methodName(){
System.out.println("测试数据");
}
}
/*
枚举方式的枚举:
推荐写法,简单高效。充分利用枚举类的特性,只定义了一个实例,且枚举类是天然支持多线程的。
借助JDK1.5中添加的枚举来实现单例模式优点:
1、不仅能避免多线程同步问题
2、还能防止反序列化重新创建新的对象
枚举方式单例是由Effective java作者Josh Bloch提倡的,结语:推荐使用!
*/
单例模式总结
1、饿汉式(静态变量)这种方式可以使用,但是没有达到 lazy loading 懒加载的效果会造成内存的浪费!开发中不建议使用。
2、饿汉式(static静态代码块)其实和第一种饿汉式(静态变量)方法差不多,其优缺点一致!唯一不同的就是把创建单例对象的操作放进了static静态代码块中
3、懒汉式(线程不安全)起到了懒加载的效果,但只能在单线程下使用。在实际开发中,不要使用这种方式!!!
4、懒汉式2(线程安全)方式线程安全但是效率太低,每次调用getInstance方法都要进行同步。所以在开发中不推荐使用。 5、懒汉式3
同步代码块(线程安全)方式在开发中不使用 ,实际上这个设计有点搞笑哈哈。
6、双重检查应用实例方式,线程安全、延迟加载、效率较高。因此开发中推荐使用!
7、静态内部类单例方式线程安全、利用静态内部类特点实现延迟加载、效率高。 开发中推荐使用这种静态内部类单例方式!
8、借助JDK1.5中添加的枚举来实现单例模式不仅能避免多线程同步问题还能防止反序列化重新创建新的对象。枚举方式单例是由Effective
java作者Josh Bloch提倡的,开发中推荐使用!
单例模式必须考虑到在多线程的应用场合下的使用,毕竟现在的服务器基本上都是多核的了。
关注公众号,为大家分享不一样的知识点!也可以留言你想知道的东西,我会尽最大努力整理。谢谢! 每天学习多一点,往后你求人说话的机会就少一点。为未来努力!