23种设计模式 - 单例模式
1.关于单例模式的一些说明
单例模式:确保一个类最多只有一个实例,提供一个全局访问点
注意:
-
单例类只能有一个实例
-
单例类必须自己创建自己的唯一实例
-
单例类必须给所有其他对象提供这一实例
单例模式可以分为两种:预加载和懒加载(即饿汉式和懒汉式)
2.两种形式详解
1.预加载(饿汉式)
预先加载。还没有使用该单例对象,但是该单例对象就已被加载到内存。
若没有使用该单例对象,该对象就被加载到了内存,会造成内存的浪费。
2.懒加载(懒汉式)
为了避免内存的浪费,可以采用懒加载,即用到该单例对象的时候再创建。
3. 单例模式和线程安全
-
饿汉式只有一条语句return instance,可以保证线程安全。但会造成内存的浪费;
-
懒汉式不浪费内存,但是无法保证线程的安全。首先,if判断以及其内存执行代码是非原子性的。其次,new Singleton()无法保证执行的顺序性。不满足原子性或者顺序性,线程肯定是不安全的,不再赘述;
-
为什么new Singleton()无法保证顺序性。创建一个对象分三步:
JVM为了提高程序执行性能,会对没有依赖关系的代码进行重排序,上面2和3行代码可能被重新排序。我们用两个线程来说明线程是不安全的。线程A和线程B都创建对象。其中,A2和A3的重排序,将导致线程B在B1处判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象(线程不安全)。
4. 懒汉式的线程安全解决方法
使用synchronized关键字。synchronized加载getInstace()函数上确实保证了线程的安全。
Synchronized是Java多线程编程中最常用的关键字。所有的Java 对象都有自己唯一的隐式同步锁。该锁只能同时被一个线程获得,其他试图获得该锁的线程都会被阻塞在对象的等待队列中直到获得该锁的线程释放锁才能继续工作。
注意:若要经常的调用getInstance()方法,不管有没有初始化实例,都会唤醒和阻塞线程。为了避免线程的上下文切换消耗大量时间,如果对象已经实例化了,我们没有必要再使用synchronized加锁,直接返回对象。
把sychronized加在if(instance==null)判断语句里面,保证instance未实例化的时候才加锁。
new一个对象的代码是无法保证顺序性的,需要使用关键字volatile保证对象实例化过程的顺序性。
即保证了懒汉式的线程安全。
Java极客思维
微信扫一扫,关注公众号