单例模式详解

单例模式是最常用的设计模式之一,目的是保证一个类只有一个实例。

在项目中的作用:

1、解决因为频繁创建对象,导致资源消耗过大的问题,如:数据库的连接池,连接池用于创建数据库连接,并对连接进行回收使用,能减少数据库连接的创建次数,从而提高效率,但是连接池对象本身在项目中只需要一个,就需要使用单例模式。类似的还有线程池等。

2、项目中能共享的工具类,如Java中的Runtime类能提供各种运行环境系统参数,它就被设计成了单例模式。

实现单例的过程:

1、要保证类只能创建一个对象,就必须隐藏类的构造方法,所以要将构造方法定义为私有的

2、在类中调用构造方法创建对象,定义静态方法用于返回对象。

下面这种单例模式属于饿汉式单例模式,既一开始就将对象实例化。这样的做法会导致性能的降低,一般我们会采用延迟加载的方式,既需要对象时再实例化。

class Hunger {

//静态的实例

private static Hunger hunger = new Hunger();

//隐藏构造方法

private Hunger(){}

//返回静态实例

public static Hunger getInstance(){

return hunger;

}

}

下面这种是懒汉式单例模式,既开始不实例化对象,到需要该实例时再实例化,提高了运行效率。

public class LazySingleton {

//静态的实例

private static LazySingleton single = null;

//隐藏构造方法

private LazySingleton(){

System.out.println("创建LazySingleton对象");

}

//返回静态实例

public static LazySingleton getInstance(){

if(single == null){

single = new LazySingleton();

}

return single;

}

}

线程安全问题:懒汉单例模式在单线程环境没有问题,但在多线程环境下就会出现问题。

执行代码,"创建LazySingleton对象"这句话会输出多次,也就是创建了多个对象。

for(int i = 0;i < 100;i++){

new Thread(new Runnable(){

@Override

public void run() {

System.out.println(LazySingleton .getInstance());

}}).start();

}

分析原因:

假设线程1满足getInstance方法中single==null条件后,准备执行创建对象的代码,然后CPU被其它线程抢占,其它线程在getInstance方法中创建对象,然后线程1抢回CPU继续执行刚才未完成的创建对象代码,这样就创建了多个对象。

线程同步问题的解决方法:

1、使用同步方法

public static synchronized LazySingleton getInstance(){

if(single == null){

single = new LazySingleton();

}

return single;

}

执行刚才多线程的代码后,我们发现可以解决同步问题,但是同步方法存在的问题是每个线程进入后都会加锁,执行效率低。

2、使用同步代码块配合if使用

public static LazySingleton getInstance(){

if(single == null){

synchronized (LazySingleton.class) {

single = new LazySingleton();

}

}

return single;

}

同步块和if语句配合使用,解决了每次都执行上锁导致的性能问题,但是运行代码后我们会发现,多线程同步的问题还是可能出现,原因是多个线程还是可能会同时进入if语句。

3、使用双重判断

在同步块中再添加一次if判断就解决了上面的问题,因为就算多个线程同时进入外层if语句,执行同步块还是要进行一次判断,这样第一个线程创建对象后,后面的线程就不能再创建了。

public static LazySingleton getInstance(){

//外层的if主要作用是判断是否需要执行同步块,提高性能

if(single == null){

//静态方法中将类作为锁

synchronized (LazySingleton.class) {

//判断对象是否为空,为空就创建对象

if(single == null){

single = new LazySingleton();

}

}

}

return single;

}

4、静态内部类

这种方法结合了饿汉式和懒汉式的特点,如果不调用getInstance方法, 静态内部类中的创建对象代码不会执行,调用getInstance后才会执行,也就有了懒汉式延迟加载的效果,并且由于对象是直接创建的,还不存在线程同步问题。

class MySingleton{

private MySingleton() {

}

private static class SingletonHelp {

static MySingleton instance = new MySingleton();

}

public static MySingleton getInstance() {

return SingletonHelp.instance;

}

}

总结:单例模式的实现一般有饿汉式、懒汉式和静态内部类等方式,饿汉式创建对象的性能比较低但不存在线程同步问题,懒汉式由于是延迟加载性能更高,但存在线程同步问题,需要使用双重判断解决,静态内部类的方式也能实现单例模式但是代码可读性稍差。

如果项目对性能不敏感推荐使用饿汉式,如果是单线程环境可以使用一般的懒汉式,如果需要多线程则可以使用多重判断的懒汉式或静态内部类实现。


分享到:


相關文章: