Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

liusxg/java-patterns

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

4 Commits

Repository files navigation

Java 设计模式

目录

  • 单例模式
    *定义
    *使用场景
    *要点
    *线程安全
    *单例模式另外两种实现

单例模式

定义

保证一个类只有一个实例,并且提供一个访问这个实例的全局访问点。

使用场景

一般用在一些本质上具有唯一性的系统资源,比如资源管理器这些。

要点

1.私有化构造器
2.提供一个静态方法用来获取类的实例

单例模式根据实例的生成时间又分成两类,懒汉式和饿汉式

懒汉式:只有需要用到的时候才会去生成实例。
饿汉式:先生成实例,不管有没有用到。

线程安全

这里的线程安全的意思是保证在多线程运行下,保证只生成一个实例。
饿汉式在类加载的时候就生成了实例,是天生的线程安全。 不过饿汉式的缺点也很明显,因为会先生成实例,并不管这个实例是否会被用到,会造成资源浪费的问题。所以一般采用的是懒汉式生成实例。 由于饿汉式不需要保证线程安全,所以这里的线程安全基本上说的是保证懒汉式的线程安全。

单例模式保证线程安全参考
[1]: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html "双重锁失效的可能性"

第一种方法:在静态方法getInstance,加上synchronized同步锁
特点

每次调用getInstance方法都要获取Singleton3.class的锁,然而只有在第一次获取实例的时候才有必要获取锁,后续的多线程访问效率会很低。

第二种方法:双重锁保证简称DCL。
特点

在获取锁之前检查一下实例是否存在,在获取锁之后再做一次检查。如果实例已经生成,后续的线程无需再获取锁。

注意
双重锁对于懒加载来说是一种比较有效的实现方式,但在java里面却行不通。在Java的优化编译器或共享内存多处理器中,这个双重检查锁作用会失效。最关键的原因在于,优化编译器的处理下,对象初始化和将对象地址写到instance的顺序是不确定的。 这个意味着instance可以先赋值,然后,Singleton对象再初始化,这并不是一个原子操作。

public static Singleton4 getInstance() {
 if (singleton4 == null) { //1
 synchronized (Singleton4.class) { //2
 if (singleton4 == null) { //3
 singleton4 = new Singleton4(); //4
 }
 }
 }
 return singleton4; //5
 }

singleton4 = new Singleton4(); //4

这个操作不是一个原子操作,这一步的原子操作有

分配内存空间
给singleton4引用赋值//在这一步的时候instance就已经不是null了
new Singleton4()

举个例子
现在有两个线程,线程I和线程II。 当线程I执行代码1的时候,此时singleton4是null,线程1可以执行到代码2获得到锁,继续执行到代码4。 当singleton4引用已经被赋值,但是还没有调用Singleton4构造函数初始化的时候,线程II执行代码1获得到的是singleton4 != null, 然后直接执行代码5,返回的是一个尚未初始化的实例,是一个不正确的结果。

使用happen-before规则重新审视DCL

happen-before规则,A happen-before B,操作A在操作B发生之前要发生,操作A对内存施加对影响要被B观察到。在这,线程I初始化Singleton4对象 应该happen-before 线程II的代码1的执行。

修复DCL:volatile关键字

volatile作用有两个

1.保证不同线程对这个变量操作时对可见性,即一个线程修改了某个变量的值,这个新值对所有线程来说是立即可见的。 2.禁止指令重排。

这里的禁止指令重排有两个含义

1.当程序执行到volatile变量的读操作或者写操作的时候,在其前面的操作必须是已经全部完成的,且结果对后面对操作也是可见的。
2.在进行指令优化的时候,不能对volatile变量访问的语句放在其后面执行,也不能将volatile变量后面的语句放在前面执行。

修复代码

 private volatile static Singleton4 singleton4 = null;
 /**
 * 私有化构造器
 */
 private Singleton4() {}
 /**
 *
 * 提供一个公有静态方法获取实例, 不管任何对象要获取Singleton类的实例都是从这个方法获取
 *
 * 懒汉式,只有需要的时候才会去生成实例
 *
 * 特点,双重锁检查, 使用volatile关键字来保证singleton4在对象初始化完成后修改值立马可见
 *
 * @return
 */
 public static Singleton4 getInstance() {
 if (singleton4 == null) {
 synchronized (Singleton4.class) {
 if (singleton4 == null) {
 singleton4 = new Singleton4();
 }
 }
 }
 return singleton4;
 }

单例模式另外两种实现

About

Java设计模式

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /