应用背景:比如有的时候系统只需要有一个全局对象,在某个系统的服务器程序中,我们将服务器的配置信息存放在一个文件中,然后这个配置数据由一个单例对象同一读取,然后服务器进程中的其他对象就可以同个这个单例的对象获取这些配置信息。
(有点像项目中使用@Component把配置properties加载进去public static final这种常量中 )
Singleton单例模式,单例的类要求只能有一个实例存在,包含两种:懒汉式和饿汉式。
懒汉式:全局的单例实例在第一次被使用的时候创建
饿汉式:全局的单例模式在类装载的时候被创建 (超级饿,所以类装载就要有吃的了~)
特点:
1. 单例模式只能有一个实例。
2. 单例类必须创建自己的唯一实例。
3. 单例类必须向其他对象提供这一实例。
注意事项:单例模式必须有一个提供给外部的静态方法,进行判断,如果我们调用这个方法的时候,如果该类持有的引用不为空,那么久返回这个引用,如果为空,那么创建该类的实例,并且将实例里的引用赋予给该类保持的引用。(所以始终应该有提供给外界的方法,并且应有一个返回对象)
缺点:多线程中应该小心一点使用,如果唯一实例没有创建,然后两个线程同时调用,都同时没有检测到唯一实例的存在,各自都创建了实例,那么就有两个实例被构造了。(需要使用互斥锁)
在编写单例模式时候遇到的小坑:
1.单例模式必须显示的构造函数是私有化的,否则默认的构造函数是public的如果不声明为private的话,就可以用
Singleton singleton1 = new Singleton();来进行调用
2.单例模式是否可以被继承?(想到这个问题的时候惊了~因为private的构造函数不能被继承),但是书上说单例模式在某些特别操作之后,是可以继承的~暂时没想明白
不能被继承的类,String StringBuffer Scanner,这些用final修饰的类,叫做常量类。
3.在static方法中不能调用对象的属性和方法(因为对象的属性和方法在直接调用时还没有初始化,但static修饰的属性和方法不需要初始化)
4.对于抽象类:
抽象类子类继承必须实现父类的抽象方法,除非父类也是抽象类;抽象类不能被实例化;抽象的方法必须定义在抽象类中; 抽象类中的方法不一定都是抽象的方法(比如set get 方法)
引入一个知识点:
static | final | static final的区别
final(强调不变性):修饰的方法相当于一个常数,创建之后就不能被修改。
修饰的属性一旦初始化就不能更改,但是初始化的时间也许是在编译期,也可能是在运行期
final 修饰的方法不可以被子类中被重写,final修饰的类不能够被继承
static(强调唯一性) :初始化在编译期也就是在类加载的时候,初始化之后可以进行改变;
static 强调只有一个,所以根据提对象无关,不可以修饰局部变量
static final : 表示方法不能重写,可以在不new的时候进行调用
写法:
饿汉式:
public class Singleton {
//饿汉式
private String age;
private final static Singleton INSTANCE = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return INSTANCE;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
//避免了线程同步问题,在类装载就完成了实例化~缺点可能造成内存的浪费
class test {
public static void main(String[] args) {
Singleton.getInstance().setAge("10");
System.out.println(Singleton.getInstance().getAge());
}
}
public class Singleton {
/**
* 实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。
* 等同上一种
* @饿汉式
* */
private String age;
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
class test {
public static void main(String[] args) {
Singleton.getInstance().setAge("10");
System.out.println(Singleton.getInstance().getAge());
}
}
public class Singleton {
/**
* 这种写法可以实现Laze loading,所谓懒加载其实就是需要的时候才进行加载,节省内存
* @缺点 : 只能在单线程使用,如果多线程的话,可能同时进行了new操作,出现了两个实例
* @懒汉式
*/
private static Singleton singleton;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
public class Singleton {
/**
* @懒汉式
* @特点 :使用了同步方法,所以线程安全了
* @缺点 : 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步,但是实际上就是目的是第一次没有创建的时候不能有
* 多线程同时进行创建的情况,其他时候想就直接进行return就好了,没必要还进行同步
* */
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
public class Singleton {
/**
* @懒汉式(同步代码块) (不可用) 属实弟弟写法
* @特点 :使用了同步方法,所以线程安全了
* @缺点 :这种同步并不能起到线程同步的作用。
* 假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
* */
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
推荐写法:
public class Singleton {
/**
* @懒汉式 (可以这样写)
* @特点 :线程安全;延迟加载;效率较高。
* */
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
使用内部静态类
public class Singleton {
/**
* @懒汉式 (静态内部类)
* @特点 :这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
* 不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,
* 而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
* 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
*
* 优点:避免了线程不安全,延迟加载,效率高。
* */
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
枚举来实现单例模式:不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
public enum Singleton {
INSTANCE;
public void method() {
}
}