单例模式与线程安全
概念:
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个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:对于不清楚加入同步代码块时,为什么还会出现线程不安全的情况,可留言讨论

浙公网安备 33010602011771号