单例模式是指不允许用户掉构造方法自己创建,通过静态方法获取对象,且获取的是同一个实例。
case1:
public class DCLDemoCase1 {
private static DCLDemoCase1 instance=new DCLDemoCase1();
private DCLDemoCase1() {
};
public static DCLDemoCase1 getInstance() {
return instance;
}
private int m=1;
public void m() {
System.out.println(m);
}
}
public static void main(String[] args) {
DCLDemoCase1 instance2 = DCLDemoCase1.getInstance();
DCLDemoCase1 instance3 = DCLDemoCase1.getInstance();
System.out.println(instance2==instance3);
}
测试结果:true。两个对象相同。
case2:
在语句private static DCLDemoCase1 instance=new DCLDemoCase1();
中新创建的这个对象还没被用上,上来直接创建这个对象,有时候创建一个对象比较浪费时间,能不能把创建的这个过程放在一个方法里需要的时候再创建。
于是就有了第二种写法:
public class DCLDemoCase2 {
private static DCLDemoCase2 instance;
private int m=1;
private DCLDemoCase2() {
};
public static synchronized DCLDemoCase2 getInstance() {
if(instance==null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance=new DCLDemoCase2();
}
return instance;
}
public void m() {
System.out.println(m);
}
}
多线程情况下测试能达到运行效果,都是同一个对象。
插曲:
但也发现:确实在缺少Thread.sleep(1)休眠下:
public class DCLDemoCase2 {
private static DCLDemoCase2 instance;
private int m=1;
private DCLDemoCase2() {
};
public static DCLDemoCase2 getInstance() {
if(instance==null) {
instance=new DCLDemoCase2();
}
return instance;
}
public void m() {
System.out.println(m);
}
}
也会达到预期效果,理论上会出现不一样的对象,可能确实在这种情况下不会有不同线程同时进入到if里创建出不同的对象。
接着case2
case3:
但是这种直接把整个方法全锁了,这种锁太粗了,在锁的优化中,有一种就是锁细化。
public class DCLDemoCase2 {
private static DCLDemoCase2 instance;
private int m=1;
private DCLDemoCase2() {
};
public static DCLDemoCase2 getInstance() {
if (instance == null) {
synchronized (DCLDemoCase1.class) {
instance = new DCLDemoCase2();
}
}
return instance;
}
public void m() {
System.out.println(m);
}
}
多线程下发现会出现不同对象,达不到单线程的效果。
这是因为:初始时每个线程的初始instance=null,所以会有数个线程进入到if(instance==null)里,于是他们会得到不同的对象。
case4:
双重锁dcl
public class DCLDemoCase4 {
private static DCLDemoCase4 instance;
private int m=1;
private DCLDemoCase4() {
};
public static synchronized DCLDemoCase4 getInstance() {
if (instance == null) {
synchronized (DCLDemoCase1.class) {
if(instance==null) {
instance = new DCLDemoCase4();
}
}
}
return instance;
}
public void m() {
System.out.println(m);
}
}
当有多个线程进入第一次判空后,那么就算第一个线程创建好对象,instance不为空,这些线程就有资格进入锁(synchronized)中创建对象,但当第一个线程进入锁中创建好对象,已经给instance赋值,instance不为空,其他线程(进入第一次判空除了第一个线程)只能进入到锁中,但无法逾越第二次判空,无法创建新线程。
case5:
程序显得完美无缺,但是instance需要volatile修饰吗?
确实需要。
创建一个对象大概经过3步(只是大概):)(1)在堆中开辟一块内存【new】(2)构造方法…初始化【invokespecial】(3)对象变量指向这块内存【astore】
**那么可能会有这种情况:**指令重排序
在线程1开辟好内存,类中m的默认值为0,然后直接赋值给类对象t,有两个线程,第一个线程先过来发生指令重排序,没来的及给m赋值,第二个线程过来判断t不为空直接使用,第二个线程拿到的是不正确的对象实例。
package doubleCheckLock;
public class DCLDemoCase5 {
private static volatile DCLDemoCase5 instance;
private int m=1;
private DCLDemoCase5() {
};
public static synchronized DCLDemoCase5 getInstance() {
if (instance == null) {
synchronized (DCLDemoCase1.class) {
if(instance==null) {
instance = new DCLDemoCase5();
}
}
}
return instance;
}
public void m() {
System.out.println(m);
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(DCLDemoCase2.getInstance().hashCode());
}).start();
}
}
}
在这里volatile表示禁止指令重排序。