我们熟知的单例模式有两种一种饿汉式,另外一种是懒汉式
"饿汉式"单例模式,思想是利用空间换时间,实现比较简单
//单例饿汉式
public class T {
//构造私有
private T(){}
//创建唯一的对象
private static final T t = new T();
//提供唯一的方式让外界获取
public static T getInstance(){
return t;
}
public static void main(String[] args) {
// 这一种没有多线程安全问题
for (int i = 1; i <= 100 ; i++) {
new Thread(){
@Override
public void run() {
//我们通过hashCode() 来判断是不是同一个对象
System.out.println(T.getInstance().hashCode());
}
}.start();
}
}
}
效果如图(部分),这个单例模式不会出现线程安全问题
"懒汉式"单例模式:懒汉式单例模式的思想是利用时间换空间,会出现线程安全问题
public class W {
//不管是饿汉式还是懒汉式,我们都需要构造私有化
private W() {
}
//定义当前数据类型
private static W w;
//定义能够访问对象的方法
public static W getInstance() {
// 判断当前对象是否存在
if (w == null) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 不存在创建出唯一的一个对象
w = new W();
}
return w;
}
public static void main(String[] args) {
for (int i = 1; i <= 100 ; i++) {
new Thread(){
@Override
public void run() {
System.out.println(W.getInstance().hashCode());
}
}.start();
}
}
}
以上是我们懒汉式的最原始的形态,我们通过测试,会发现,在多线程的情况下我们获得的并不是同一个对象;
之后我们通过我们的同步代码块来进行修改程序,第一次修改
所以这时我们就引入了 锁的双重判断机制,就是在线程进入到锁内部的时候,再次判断对象是否存在.这样也可以提高我们的代码效率,也保证了在多线程下,创建出唯一的一个对象
我们通过测试发现,多线程下也是同一个对象,当然这也就是我们所认为的比较标准的一种单例的书写模式
但是这个懒汉式单例模式隐含着问题的,就是我们再使用双重判断到时候,可能会出现"半初始化问题",所谓的半初始化就是我们在 new对象 的时候,另外一个线程进行赋值, 但是这个不会在高并发很小的时候出现,可能上百万次也不会出现.
那么为啥会出现上面的半初始化问题,这是因为我们的代码最终会被转成我们的汇编语言进行执行,那么这里面的有些指令在多线程多cpu的情况下就会发生一种叫指令重排序的优化,目的就是在不影响最后的结果的情况下,可以调换指令的执行顺序.
那么如果我们不想让我们的代码出现以上情况,就 需要在对象的引用前添加volatile关键字,来防止多线程多cpu情况下指令重排