单例介绍
单例 模式 是一种对象的创建模式,它用于生产一个对象的具体实例,它看可以确保系统中一个类只才产生一个实例。。 实现的单例是在虚拟机的范围内,因为装在类ClassLoader 他是虚拟机的,一个虚拟机在通过自己的 classload 装载实现单例类的时候,就会创建一个类的实例。 在java 中:
1 对于频繁使用的对象,可以省略创建对象所花费的时间。
2 减少 new -----》减少 gc ,
单例的六种
- 饿汉模式
有一个private 的构造函数,mInstance 是 static 的,同事他的getMInstance 也是 static的。
不能 对 mInstance 做延迟加载,比如单例的创建过程有时候会很慢,变量初始化内容特别多时候,所以在加载这个类的时候,就会被执行,任何用得到类的地方就会初始化这个变量。
public class HungurySingleton {
private static final HungurySingleton mHungurySingleton = new HungurySingleton();
private HungurySingleton(){
System.out.println("Singleton is create");
}
public static HungurySingleton getHungurySingleton() {
return mHungurySingleton;
}
public static void createString(){
System.out.println("createString in Singleton");
}
public static void main(String[] args){
HungurySingleton.createString();
}
}
- 懒汉模式
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() {
// 第一次调用的时候会被初始化
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
public static void createString(){
System.out.println("create String");
}
public static void main(String[] args){
LazySingleton.createString();
}
}
多线程并发下,无法保持 实例是唯一的。
因为getInstance 不是同步的
所以做 懒汉线程安全问题的 单例
//方法中声明synchronized关键字
public static synchronized LazySafetySingleton getInstance() {
if (instance == null) {
instance = new LazySafetySingleton();
}
return instance;
}
//同步代码块实现
public static LazySafetySingleton getInstance1() {
synchronized (LazySafetySingleton.class) {
if(instance == null){//懒汉式
instance = new LazySafetySingleton();
}
}
return instance;
}
以上 性能有问题。
优化:DCL
双检查锁机制。。
public class DclSingleton {
private static volatile DclSingleton mInstance = null;
// private static DclSingleton mInstance = null;
private DclSingleton() {
}
public void doSomething() {
System.out.println("do sth.");
}
public static DclSingleton getInstance() {
// 避免不必要的同步
if (mInstance == null) {
// 同步
synchronized (DclSingleton.class) {
// 在第一次调用时初始化
if (mInstance == null) {
mInstance = new DclSingleton();
}
}
}
return mInstance;
}
}
mInstance = new DclSingleton(); 这不是一个原子操作
原子操作(atomic operation)是不需要synchronized",这是Java多线程编程的老生常谈了。所谓原子操作是指不会被[线程调度]机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch 切 换到另一个线程
new 过程 是 先创建内存,然后执行 构造方法,然后指向 那块内存。
但是JVM在即时编译器中,会存在一个指令重排的优化,也就是以上说的三步,他不会按照我们想要的顺序执行,有可能第一步在 第二步之前。
所以把, mINstance 使用 volatile 关键字就好了。
因为它 的可见性,也就说他能保证线程在本地不会存在 instance 的副本,而每次都会去主内存中读取,防止指令重排。
JVM 的即时编译器中存在指令重排的优化
静态内部类
JVM 提供给我们的同步控制
static final
public class StaticInnerSingleton {
private StaticInnerSingleton() {
}
public static StaticInnerSingleton getInstance() {
return SingletonHolder.sInstance;
}
// 静态内部类
private static class SingletonHolder {
private static final StaticInnerSingleton sInstance = new StaticInnerSingleton();
}
}
调用构造方法时,外部类Outer被加载,但这时其静态内部类StaticInner却未被加载。直到调用该内部类的静态方法(在分割线以下),StaticInner才被加载。可以做类似的实验验证非静态内部类的情况。
结论:加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生
内部类(不论是静态内部类还是非静态内部类)都是在第一次使用时才会被加载。 对于非静态内部类是不能出现静态模块(包含静态块,静态属性,静态方法等) 非静态类的使用需要依赖于外部类的对象
保证了线程安全,保证了性能。
但真正保证线程安全的原因是,虚拟机加载内部类的时候 classloader 就已经给加锁了
枚举
public enum EnumSingleton {
//定义一个枚举的元素,它就是 Singleton 的一个实例
INSTANCE;
public void doSomeThing() {
// do something...
}
}
枚举
enum Type{
A,B,C,D;
static int value;
public static int getValue() {
return value;
}
String type;
public String getType() {
return type;
}
}
在原有的基础上,添加了类方法和实例方法。我们把Type看做一个类,
那么enum中静态的域和方法,都可以视作类方法。和我们调用普通的静态方法一样,
这里调用类方法也是通过 Type.getValue()即可调用,访问类属性也是通过Type.value即可访问。
下面的是实例方法,也就是每个实例才能调用的方法。那么实例是什么呢?
没错,就是A,B,C,D。所以我们调用实例方法,也就通过 Type.A.getType()来调用就可以了。
最后,对于某个实例而言,还可以实现自己的实例方法。再看下下面的代码:
enum Type{
A{
public String getType() {
return "I will not tell you";
}
},B,C,D;
static int value;
public static int getValue() {
return value;
}
String type;
public String getType() {
return type;
}
}
枚举实现单例
class Resource{
}
public enum SomeThing {
INSTANCE;
private Resource instance;
SomeThing() {
instance = new Resource();
}
public Resource getInstance() {
return instance;
}
}
上面的类Resource是我们要应用单例模式的资源,具体可以表现为网络连接,数据库连接,线程池等等。
获取资源的方式很简单,只要 SomeThing.INSTANCE.getInstance() 即可获得所要实例。下面我们来看看单例是如何被保证的:
首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。
也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。
Android 中的 单例
- application
网友评论