单例模式的思想就是某一个对象的只存在一个!
懒汉式(线程不安全)
public class Singleton{
private static Singleton instance;
//这里将构造方法设置成私有的就保证了外部不能直接new出对象来
private Singleton(){
}
public static Singleton getInstance(){
if(instance==null){
instance= new Singleton();
}
return instance;
}
}
懒汉式(线程安全)
public class Singleton{
private static Singleton instance;
private Singleton(){}
//这里 synchronized 锁定的是static修饰的方法 所以他锁定的是Singleton.class 而不是this对象实例了
public static synchronized Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
饿汉式
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
双重校验锁方式
以上的方法的目的就是保证我们获取的某一个类的实例永远只有一个;
在单线程模型中以上方法是可以确保单例的;
虽然懒汉式有线程安全的方式但是在java中的多线程模型中这种方式就真的能保证是单例模式么?下面我们来分析一下:
- 非线程安全的不能保证多线程模型下的业务
直接添加synchorizon,尽管这样做到了线程安全,并且解决了多实例问题,但并不高效。在任何调用这个方法的时候,你都需要承受同步带来的性能开销,然而同步只在第一次调用的时候才被需要,也就是单例类实例创建的时候
所以我们就需要一种既能保证线程安全又能保证性能开销的方法:
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchoronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
这里我们看的话 好像是能保证了只存一次的锁定线程的问题 就是第一次对象为null需要new的时候,其他的时候就不会走线程锁代码块了;
但是依然存在的一个问题就是java平台的内存模型的问题:
当我们new一个对象的时候的伪代码:
mem = allocate(); //1.分配内存空间
instance = mem; //2.内存地址☞向我们的instance引用
ctorSingleton(instance); //3.调用构造方法来初始化对象
//现在的问题是 我们不能保证的过程是2和3过程 可能是2->3也可能是3-2
//在多线程 并且出现2->3的情况的话就会出现问题:
//Thread1获取锁new一个instance并且之后释放了锁,但是此时执行到了2步骤
//Thread2判断的话因为此时instance已经有了地址的引用了,所以直接返回了
//此时调用instance的话是很明显是会有问题的;DCL失败
在JDK1.5之后为我们提供了一种解决办法就是使用volatile关键字
public class Singleton{
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchoronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
线程中变量在内存中的变化:从主存中加载进线程栈中,拷贝一份,然后改变的话是先改变拷贝的那份变量然后再写到主存中去;
//这里有个例子
public class Test{
public static void main(args[]){
Thread1 thread = new Thread1();
thread1.start();
Thread.sleep(2000);//主线程睡眠两秒
thread.flag = true;
System.out.println("Thread:"+thread.i);
}
}
class Thread1 extends Thread{
public boolean flag = false;
pbulic int i = 0;
@Overide
private void run(){
while(!flag){
i++;
}
}
}
运行结果:909823423
打印结果说明不了设么,大家看你的控制台会发现程序没有运行结束,依然有线程在跑,很明显就是子线程了;
还有一个点是:把!flag 改为 flag==true的话线程就会结束了;
或者在声明flag变量的时候使用volatile关键字;
volatile关键在保证了每次线程中的变量都是内存中的最新的值;
但是volatile并不能完全保证原子性,后续查资料分析;
枚举的方式
public enum Singleton{
INSTACNE;
}
通过:Singleton.INSTANCE来访问实例
创建枚举默认就是线程安全的