项目敏捷管理流程

单例模式

单例模式指的是在应用整个生命周期内只能存在一个实例。单例模式是一种被广泛使用的设计模式。Java中基础的基础就属于Java的单例了,下面分别介绍几种单例模式

饿汉式

饿汉子饥饿,所以非常想要这个单例,其实就是全局的单例实例在类加载的时候实现

  • 优点:一上来就加载,不用主动调用。

  • 缺点:占资源 ,适合于单例内内容少的资源。

那么思考一些如何在类加载的时候实现呢?最好的办法是使用静态变量,那么我们有了如下写法

1
2
3
class Singleton{
public staitc Singleton Instance = new Singleton();
}

但是这样的问题是如果我们想new这个类的时候也是可以的,就失去了单例的本质,接下来是变形

1
2
3
4
class Singleton{
public staitc Singleton Instance = new Singleton();
private Singleton();
}

这样就解决了new的问题了,想想我们还有哪些东西漏了?对,就是获取单例的方式,加上

1
2
3
4
5
6
7
class Singleton{
public static Singleton Instance =new Singleton();
private Singleton();
public Singleton getInstance(){
return Instance;
}
}

懒汉式

懒汉式很懒,懒到要主动调用的时候才会去操作!也是懒加载,程序在使用的时候才去调用,这样保证了内存不会被浪费

  • 优先:适用于单例内内容较多的情况

  • 缺点:需要主动调用才去加载,对于一上来就需要单例的类不适用

首先我们简单的写一下单例模式

1
2
3
4
5
6
7
8
9
10
class Singleton{
private static Singleton Instance = null;
private Singleton();
public static Singleton getInstance(){
if(Instance = null){
Instance = new Singleton();
}
return Instance;
}
}

这样做的时候也没有问题,问题是在多线程工作情况下,Instacne都会判断为null,那么两个线程就会分别创建出一个实例来,这就不是单例了。为了保证同步我们用synchronized

1
2
3
4
5
6
7
8
9
10
class Singleton{
private static Singleton Instance = null;
private Singleton();
public statics synchorized Singleton getInstance(){
if(Instance == null){
Instance = new Singleton();
}
return Instance;
}
}

那么加上synchorized关键字之后,就被锁上了,那么两个线程同时进入的时候就会卡主,其中一个持有同步锁继续执行,另一个线程需要等,等第一个执行完了之后,第二个进入,这也就解决了之前的问题了。

这时就出现了另一个问题,如果有很多线程同事访问的话,那么所有的线程都会等待

那么就有了一个双重检查的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton{
private static Singleton Instance = null;
private Singleton();
public statics Singleton getInstance(){
if(Instance == null){
synchorized(Singleton.class)
if(Instance == null){
Instance = Singleton();
}
}
return Instance;
}
}

这样就很好的保证了之后进来的线程不需要那么长时间点的等待,这样看起来就完美了,但是,如果有非原子操作的话如何进行?

原子操作

顾名思义,原子是不可分割的,(其实应该叫量子操作哈哈)

如向一个int操作的赋值是原子操作n = 6而声明变量则不办事一个原子操作int n = 6,因为其包含了声明、复制两步操作,那么在多线程的过程中,有没有这样的情况,声明了但是还没赋值

并且计算机为了提高效率,会进行指令重排如:

1
2
3
4
int n ;
n =1;
int b = 9;
int c = n + b;

正常顺序是1234,由于指令重排可能出现4231,并且3 ,4也没有原子性,也会进行指令重排

那么回到之前的代码

1
Instance = Singleton();

这一行的正常顺序是

  • 给Singleton分配内存
  • 调用Singleton构造函数初始化变量,形成实例
  • 将Singleton对象指向内存的分配

那么在实际的情况中,2 3的步骤可能进行重排,因此可能Singlton已经比不为空了,但是这时候那么锁就打开了(Singleton不为空了但是初始化并没有完成的时候)就会产生问题。

这里就是线程1没有完成写的操作,但是线程2就完成了读取的操作。

那么最终版的懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton{
public static volatile Singleton Instance = null;
private Singleton();
public Singleton getInstance(){
if(Singleton == null){
synchorized(Singleton.class){
if(Singleton == null){
Instance = new Singleton();
}
}
}
}
return Instance;
}

大名鼎鼎的EventBus中,其入口方法EventBus.getDefault()就是用这种方法来实现的。

Instance是在类加载的时候进行的,而类的加载是由ClassLoader来做的,所以开发者很难把握

  • 初始化太早,造成资源浪费
  • 初始化依赖于其他的程序,那么很难保证它在初始化之前做好。

那么在new对象,反射对象,加载子类,会先加载父类,jvm的主类的时候,会触发类的加载机制。

静态内部类

1
2
3
4
5
6
7
8
9
class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton();
public static final getInstance(){
return SingletonHolder.INSTANCE;
}
}

对于内部类来说,其实一个饿汉式

在SingletonHolder初始化的时候由ClassLoader来保证同步,并且由于Instance是静态的,使Instance成为一个单例。

堪称完美

枚举单例

1
2
3
4
5
6
public enum SingleInstance(){
INSTANCE;
fun(){

}
}

由于枚举创建的过程是线程安全的,其简单,又解决了大部分问题

谢谢您的鼓励~