单例模式与线程安全

 概念:

       单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。单例模式主要有饿汉模式和懒汉模式(延迟加载),特点如下:

  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。

 

饿汉模式:(在使用类的时候就将类加载,属于线程安全),代码如下

public class Singleton {    
    public static Singleton singleton = new Singleton();
    public Singleton(){}
    public static Singleton getInstance(){
        return singleton;
    }    
    
    public static void main(String[] args) {
        for(int i=0; i<10 ; i++){
            new Thread(new MyRunnable(),"A"+i).start();
        }
    }
}
public class MyRunnable implements Runnable {
	
	@Override
	public void run() {		
	  //打印线程名称和返回单例的哈希值
	  System.out.println(Thread.currentThread().getName()+" :" +Singleton.getInstance().hashCode());		
		
	}
}

此时打印出来的结果如下(可见取得是同一实例): 

  

 

懒汉模式:(延迟加载,在获取实例的时候,才去创建)

public class Singleton {    
    public static Singleton singleton = null;
    public Singleton(){}
    
    //获取实例时才创建
    public static Singleton getInstance(){    
        if(singleton==null){        
            try {
                //这里暂停1秒钟,模拟创建实例前的初始化工作
                Thread.sleep(1000);
                //创建对象
                singleton = new Singleton();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return singleton;
    }    
    
    public static void main(String[] args) {
        for(int i=0; i<10 ; i++){
            new Thread(new MyRunnable(),"A"+i).start();
        }
    }
}
public class MyRunnable implements Runnable {
    
    @Override
    public void run() {        
      //打印线程名称和返回单例的哈希值
      System.out.println(Thread.currentThread().getName()+" :" +Singleton.getInstance().hashCode());        
        
    }
}

此时打印出来的结果如下:(可见其实取得不是同一实例):

 

 对于线程安全问题,一般处理方法就是为该方法加入关键字synchronized( 即:synchronized public static Singleton getInstance(){} ),此时确实能解决线程安全问题,但是,该方法性能不高,于是我这里才有同步代码块的方式,代码如下:

public class Singleton {    
    public static Singleton singleton = null;
    public Singleton(){}
    
    //获取实例时才创建
    public static Singleton getInstance(){    
        if(singleton==null){        
            try {
                //这里暂停1秒钟,模拟创建实例前的初始化工作
                Thread.sleep(1000);
                //创建对象
                synchronized(Singleton.class){
                    singleton = new Singleton();
                }    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return singleton;
    }    
    
    public static void main(String[] args) {
        for(int i=0; i<10 ; i++){
            new Thread(new MyRunnable(),"A"+i).start();
        }
    }
}

但此时还是出现线程不安全的因素,这是因为有可能两个或三个线程同一时间执行到了synchronized(Singleton.class),第一个获取锁的线程new了一个实例,等第一个线程释放锁的时候,第二个线程继续获取锁并从新new一个实例。

想要解决线程安全,只需要采用DCL双锁机制即可,代码如下:

 

public class Singleton {    
    public static Singleton singleton = null;
    public Singleton(){}
    
    //获取实例时才创建
    public static Singleton getInstance(){    
        if(singleton==null){        
            try {
                //这里暂停1秒钟,模拟创建实例前的初始化工作
                Thread.sleep(1000);
                //创建对象
                synchronized(Singleton.class){
                    if(singleton==null){    
                        singleton = new Singleton();
                    }
                }    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return singleton;
    }    
    
    public static void main(String[] args) {
        for(int i=0; i<10 ; i++){
            new Thread(new MyRunnable(),"A"+i).start();
        }
    }
}

 

 

 

PS:对于不清楚加入同步代码块时,为什么还会出现线程不安全的情况,可留言讨论

posted @ 2017-03-22 00:48  开着坦克的瑞兽  阅读(177)  评论(0)    收藏  举报