单例模式
定义
单例模式确保一个类只有一个实例,并提供一个全局访问点。
代码实现
有如下几种实现单例模式的方法:
急切实例化
使用“急切”创建实例,而不用延迟实例化的做法。在静态初始化器中创建单例,保证了线程安全。
public class Singleton { // √ private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
同步getInstance()方法
懒汉式:当客户端需要获取对象时进行首次实例化,需要考虑多线程的问题。
class Singleton2 { private static Singleton2 instance; private Singleton2() {} public static synchronized Singleton2 getInstance() { if (instance == null) { instance = new Singleton2(); } return instance; } }
同步getInstance()的做法将拖垮性能(同步一个方法可能造成程序执行效率下降100倍),一般不采用这种做法。
双重检查加锁
懒汉式,而且性能要比同步getInstance()方法好。
class Singleton3 { // √ private volatile static Singleton3 instance; private Singleton3() { } public static Singleton3 getInstance() { if (instance == null) { synchronized(Singleton3.class) { if (instance == null) { instance = new Singleton3(); } } } return instance; } }
volatile关键词确保:当instance变量被初始化成Singleton3实例时,多个线程正确地处理instance变量。
volatile与synchronized
(1) volatile
可见性是指一个线程修改了共享变量的值,其它线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此,可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。
(2) synchronized
看下面的一段代码:
private int i3; synchronized int geti3() {return i3;}
首先,synchronized获得并释放监视器——如果两个线程使用了同一个对象锁,监视器能强制保证代码块同时只被一个线程所执行。
但是,synchronized也同步内存:事实上,synchronized在“主”内存区域同步整个线程的内存。因此,执行geti3()方法做 了如下几步:
1> 线程请求获得监视this对象的对象锁(假设未被锁,否则线程等待直到锁释放)
2> 线程内存的数据被消除,从“主”内存区域中读入(Java虚拟机能优化此步。。。[后面的不知道怎么表达,汗])
3> 代码块被执行
4> 对于变量的任何改变现在可以安全地写到“主”内存区域中(不过geti3()方法不会改变变量值)
5> 线程释放监视this对象的对象锁
(3) volatile 与 synchronized 的区别
1> volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住;
2> volatile仅能使用在变量级别,synchronized则可以使用在变量、方法;
3> volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性;
4> volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞;
5> volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化。