Java学习-设计模式-单例模式
概述:
单例模式是一种创建模式。
这种模式只涉及一个单独的类,它负责创建自己的对象。
该类确保只创建单个对象。
这个类提供了一种访问其唯一对象的方法。
特点:
- 单例模式的构造函数是私有的,没有办法直接使用new调用构造函数,所以不会创建新对象。它只能通过它的一个静态方法得到实例,而这个静态方法可以去调构造函数产生一个实例并返回。
- **单例模式的作用 : ** 可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问,从而方便地控制了实例个数,并节约系统资源。
- **单例模式的使用场合: ** 在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次),应该让这个类创建出来的对象永远只有一个。
理解:
这玩意有点印象,就是某个类不能直接通过构造方法 new
出来,但是可以通过调用这个类中的静态方法返回这个类的对象。在这个类的静态方法中,会判定该类是否已经创建过对象,如果没有则创建,创建过,则返回之前创建的对象。
启动服务器的时候是怎样加载这个单例类的?
摘抄自:阿丙的博客园
Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。
其实所谓的单例模式并不是他的对象永远就只有一个,一个类的对象在我们需要的时候可以随时创建,但是我们不需要管实例的销毁,因为垃圾管理器会自动销毁,这也是java语言的一个特点之一;但是单例模式就是在该类在已经创建一个对象的情况下不允许在创建另一个对象,所以你在使用线程的时候要注意单例类的锁定,不允许2个线程中同时调用一个单例类,应为这2个线程的实例会被创建2次也就是2个不同的实例,这违反了单例模式的约束;
在启动服务器的时候该类的实例并没有产生;但是该类会在 java.lang.Class中有一份他自己的字节码。
单例对象的创建与销毁
之前想的是调用就创建了,想要销毁就在类中定义一个方法,将其置为null就OK了,但是百度后得知并不是的。
单例模式并不是它的对象永远就只有一个。而是在该类已经创建一个对象的情况下,不允许再创建另一个对象。所以单例模式的对象并不是在服务启动时创建,在服务停止时销毁。它也是在用到的时候创建,不用的时候销毁。具体就是:服务启动的时候,创建的是类的字节码,在用到时候类加载器读取字节码,生成一个实例,这样就创建出了一个对象。当长时间不使用对象的时候,对象就会被垃圾器回收。下次创建的时候,依然是读取字节码。
当然在类中定义一个方法将其置为null是一个办法;但是没解决根本性问题,因为类未被卸载;并且事实上类的卸载根本不由你控制。
练习举例:
注:该练习参考了程序猿001–单例模式的八种写法比较
-
**饿汉式(因为对象只存在一个,这里的对象以静态常量存储在类中)(一般情况下可用) **
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
package Practices.Mode.Singleton_Mode; // 1.饿汉式(因为对象只存在一个,这里的对象以静态常量存储在类中)(一般情况下可用) public class Singleton1 { // 单例对象定义为静态常量 public static final Singleton1 singleton1 = new Singleton1(); private Singleton1(){} // 将构造方法私有化,禁止使用构造方法new出对象 public static Singleton1 getSingleton1(){ return singleton1; } }
-
**饿汉式(这里将对象放到静态代码块中,静态代码块只有在类加载的时候才加载,所以也只会加载一次)(一般情况下可用) **
优点:这种写法比较简单,就是在类加载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
package Practices.Mode.Singleton_Mode;
// 2. 饿汉式(这里将对象放到静态代码块中,静态代码块只有在类加载的时候才加载,所以也只会加载一次)(一般情况下可用)
public class Singleton2 {
public static Singleton2 singleton2 ; // 首先还是要定义这个对象
static {
singleton2 = new Singleton2(); // 在静态代码块中初始化它
}
private Singleton2(){} // 私有化构造方法,避免通过new创建对象
public static Singleton2 getSingleton2(){ // 通过Get方法获取对象
return singleton2;
}
}
- **懒汉式(当需要对象时去判断对象存不存在,然后根据情况返回或者创建对象)(线程不安全)(不建议在多线程下使用) **
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
package Practices.Mode.Singleton_Mode;
// 3.懒汉式(当需要对象时去判断对象存不存在,然后根据情况返回或者创建对象)(线程不安全)(不建议在多线程下使用)
public class Singleton3 {
private static Singleton3 singleton3; // 首先还是要定义这个对象
private Singleton3(){} // 在静态代码块中初始化它
public static Singleton3 getSingleton3(){ // 通过Get方法获取对象
if (singleton3 == null){ // 先判断对象是否存在,不存在则创建,存在则不管
singleton3 = new Singleton3();
}
return singleton3; // 返回单例对象
}
}
-
**懒汉式(当需要对象时去判断对象存不存在,然后根据情况返回或者创建对象)(使用线程同步,线程安全)(效率低,不建议在多线程下使用) **
解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
package Practices.Mode.Singleton_Mode; // 懒汉式(当需要对象时去判断对象存不存在,然后根据情况返回或者创建对象)(使用线程同步,线程安全)(效率低,不建议在多线程下使用) public class Singleton4 { private static Singleton4 singleton4; // 首先还是要定义这个对象 private Singleton4(){} // 在静态代码块中初始化它 public static synchronized Singleton4 getSingleton4(){ // 通过Get方法获取对象 if (singleton4 == null){ // 先判断对象是否存在,不存在则创建,存在则不管 singleton4 = new Singleton4(); } return singleton4; // 返回单例对象 } }
-
**懒汉式(当需要对象时去判断对象存不存在,然后根据情况返回或者创建对象)(虽说有线程限制,但是还是可能创建多个实例)(不建议在多线程下使用) **
由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
package Practices.Mode.Singleton_Mode; // 5. 懒汉式(当需要对象时去判断对象存不存在,然后根据情况返回或者创建对象)(虽说有线程限制,但是还是可能创建多个实例)(不建议在多线程下使用) public class Singleton5 { private static Singleton5 singleton5; // 首先还是要定义这个对象 private Singleton5(){} // 在静态代码块中初始化它 public static Singleton5 getSingleton5(){ // 通过Get方法获取对象 if (singleton5 == null){ // 先判断对象是否存在,不存在则创建,存在则不管 synchronized (Singleton5.class){ // 同步代码块 singleton5 = new Singleton5(); } } return singleton5; // 返回单例对象 } }
-
**双重检查[推荐用] **
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
优点:线程安全;延迟加载;效率较高。
volatile 关键字:被 volatile 关键字修饰的共享变量(类的成员变量、类的静态成员变量)的属性 1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 2. 禁止进行指令重排序.
package Practices.Mode.Singleton_Mode; // 6.双重检查[推荐用] // 这里有一个重要的点,就是 volatile 关键字 public class Singleton6 { private static volatile Singleton6 singleton6; // 首先还是要定义这个对象 private Singleton6(){} // 在静态代码块中初始化它 public static Singleton6 getSingleton6(){ // 通过Get方法获取对象 if (singleton6 == null){ // 先判断对象是否存在,不存在则创建,存在则不管 synchronized (Singleton6.class){ // 同步代码块 if (singleton6 == null) { // 再次判断对象是否存在,不存在则创建,存在则不管 singleton6 = new Singleton6(); } } } return singleton6; // 返回单例对象 } }
-
**静态内部类[推荐用] **
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
package Practices.Mode.Singleton_Mode; public class Singleton7 { private Singleton7() {} private static class SingletonInstance { private static final Singleton7 INSTANCE = new Singleton7(); } public static Singleton7 getInstance() { return SingletonInstance.INSTANCE; } }
-
**枚举[推荐用] **
package Practices.Mode.Singleton_Mode; public enum Singleton8 { INSTANCE; public void whateverMethod() { } }
第七和第八个有点蒙,基础还是有点差啊,越学越发现自己好多不知道的。