设计模式之单例模式(自用)

一、程序现状

}

利用线程类Thread提供的以下方法,可以实现对对每一请求进行惟一标识:

}

程序为基于Tomcat的WEB应用,在并发请求很少的情况下,程序运行正常,而当并发请求较多(70~300/30秒)时,WEB应用的页面几乎无法访问,通常需要刷新多次才可能成功访问一次。

if(instance==null){

}

    if(instance==null)

{

因为这种方法效率非常差,所以还需要另外想办法解决多线程下单例模式的问题。所以有人想到一种将同步的粒度缩小的办法,叫做双重校验锁。

观察数据库连接池,数据库连接数量已达配置文件设定最大值,但繁忙的数据库连接并不多,大部分处于空闲状态。

单例模式,顾名思义,指的是一个类只存在一个实例。

}

instance=new Singleton();

图片 1

private static  Singleton Instance=null;

{

}

{

public class Singleton{

public static Thread currentThread()   // 取得当前线程对象

    }

return m_instance;

}

{

        private static Singleton instance=new Singleton();

但是在多线程中记录log面临的问题是由于线程众多,记录的log是也是由线程混杂生成的,因而很难从中抽取出一个线程的执行log进行分析。但是,要有效地分析线程运行情况,必须从繁杂的线程中抽取出一个线程的执行log。

}

if (m_instance == null)

原因是因为,由于对象的创建不具有原子性,对象的创建可以简单的分为几个阶段1、在堆中开辟可用的空间
2、对象进行初始化
3、将对象的引用复制给引用变量,也就是“=”这个操作。java虚拟机jvm会对指令进行重排序来优化程序,jvm会保证单线程下重排序不会对结果产生影响,假设重排序可能是先把对象的引用传给引用变量,然后再进行初始化对象,就算是这样,只要保证在调用对象之前,已经完成了初始化,那么重排序将不会影响结果。但是在多线程的情况之下,这种重排序的机制将可能出现问题,假设线程1为第一个调用new
Singleton的线程,而且在它创建对象的时候发生了重排序,jvm在未将对象初始化的引用先传递给了引用变量instance,而此时,线程2也在访问getInstance(),那么当它进行instance==null判断的时候,将会认为对象已经创建(实际上对象还未初始化)而直接返回,如果此时再继续进行对象的调用,将会发生错误,因为对象没有初始化完成。而且多个线程最大权限访问同一个资源,也是会出问题的。(线程1进行对象初始化后还未进行引用赋值,线程2此时将判断对象还未创建,从而进入if子句中进行对象创建,这样就无法保证实例唯一)

二、调试与跟踪面临的问题

private Singleton(){};//私有的构造方法

具体实现过程如下:

public enum Singleton{

要在log中标识惟一线程,很容易想到的方式是在记录log时同时记录线程ID,但是对于Tomcat及类似的WEB的应用,使用线程ID存在以下两个问题:

这种方法解决了上一个方法效率的问题,也解决了只创建唯一的实例,但是由于重排序机制,将可能会导致双重校验锁失效,想到了既然问题是重排序机制下其他线程访问了未初始化完成的对象造成的可能错误。那么可以想办法让jvm不进行重排序,volatile关键字有两种语义,一个是线程间的可见性,保证其标识的值是最新的值,从内存中读取,而不是缓存的值,另一种语义是禁止重排序。所以可以用volatile解决双重校验锁失效的问题。 

return m_instance;

}

JDK1.4及之前并不支持线程ID,而当前面临的应用正好使用的是JDK1.4.2。

private Singleton(){};//私有的构造方法

Synchronized(this)

private Singleton(){};//私有的构造方法

显然,如果每一条log能够有当前线程(更确切地说,是针对当前请求的响应)的惟一标识,那么抽取一个线程就成为可能。

这样的话,类就需要持有一个自身类的实例,当需要调用者调用公共的接口想要获得这个类的实例的时候,统一的将这个实例返回。

五、总结

    if(instance==null)

public final String getName()         // 取得线程的名称

还有一种方法是静态内部类的方法,饿汉式的方式没有多线程下的诸多问题,但是由于其不具有延迟性,而不被采用,但是可以将这种类加载时由类直接创建实例的方式的优点利用,通过静态内部类的方式,这样既保证了延迟创建对象,又多线程安全。

通过Tomcat的status页面,可以发现Tomcat
当前线程数已达配置文件中设定的最大值(800个https,200个http),并且当前所有线程均处于忙碌状态,大部分线程的生存期比较长,最长的可达20分钟。

private Singleton(){};//私有的构造方法

该类是一个单实例类,通过分析其代码,并没有需要特别注意线程安全的地方,故可以直接取消方法getInstance的同步特性,只是这样可能在线程调用getInstance产生多余一个的实例,但此后它会被Java进行垃圾回收,并没有太大影响。或者,可以将同步范围缩小至需要创建对象的代码块处,这样将仅在程序刚开始运行时受到互斥的影响。

public class Singleton{

看起来似乎可以很快执行完毕的同步方法,在线程众多、被频繁调用时,也有可能是线程执行的瓶颈所在,不要将关注点只放在哪些可能比较耗时的操作上。

    return Singleton.instance; 

if (m_instance == null)

private static  Singleton Instance=new Singleton();//持有一个类的实例

单实列类的使用要慎重,其对空间占用率的影响可能远不如因同步而带来的负面影响。通常,应当只将具有状态的类设计成单实例类。

public class Singleton{

}

Singleton.INSTANCE就是singleton类型的唯一实例了。

if (m_instance == null)

public class Singleton{

}

上面的所有的类内的单例对象引用也可以定义为final。

public  static  XXX  getInstance()

那么如何解决这个问题呢?首先想到的可能是加锁,既然在对象创建的未完成的时候可能有其他的线程访问这个资源,那么可以将这个getInstance()加上synchronized关键字,每次只允许一个线程访问这个方法,这样确实杜绝了上面所说的问题,但是由于在这么大的粒度下面加上同步,将会非常影响效率。

由于怀疑是某些操作耗时过长,所以在需要记录log的方法中,入口处与出口处均需添加log。

}

m_instance = new XXX();

    }

}

    }

在请求的入口处通过currentThread方法取得线程对象,然后调用其setName方法为线程设定一个惟一标识,惟一标识根据不同的应用可以设计不同的标识,只要其符合惟一性的标准即可。

public static Singleton getInstance(){

三、日志记录解决方案

    }

}

public static Singleton getInstance(){

Tomcat等服务器会维护一个线程池,线程池中的线程会被反复使用,以便高效地响应请求,在这些种情况下,会有多个请求的log使用同一标识,依然无法抽取针对一次请求响应的log。

INSTANCE;//枚举值,java会在创建class的时候将它定义为static
final的,它的值是Singleton类型的对象

synchronized  public  static  XXX  getInstance()

        }

多线程应用,取得能够惟一标识一个线程的log是迅速查找问题的关键,单纯依据现象及经验进行分析,可能会耗费大量的时间。前期在分析问题可能产生原因的同时,能取得有效log是一项优先度极高的工作。

与这种方法不同的另一种实现叫做懒汉式,可能是因为懒汉是要到事情迫在眉睫的才会去做,而懒汉式的单例模式实现这种方式是在需要这个类的实例再去创建这个实例,是一种延迟创建对象的实现方式。

相关文章

Comment ()
评论是一种美德,说点什么吧,否则我会恨你的。。。