单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
如果我们要让类在一个虚拟机中只能产生一个对象:
- 将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。
- 提供一个public方法作为该类的访问点,用于创建该对象,必须是static修饰的,因为在类的外部开始还无法得到类的对象,只能通过类调用该方法。
- 缓存已创建的对象,否则该类无法知道是否已经创建了对象。静态方法只能访问类中的静态成员变量,所以该类对象的变量也必须定义成静态的。
注意
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
实例的步骤
- 私有的构造器
- 私有的,静态的,该类的引用
- 公共的静态的访问方式
单例模式的优点:
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
单例模式-应用场景
- 网站的计数器:一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用:一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application 也是单例的典型应用。
- Windows的Task Manager (任务管理器) 就是很典型的单例模式
- Windows的Recycle Bin (回收站) 也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
单例设计模式一般有两种方式:饿汉式和懒汉式。
其最大的区别是看其什么时候创建对象,如果一开始就创建实例对象,则是饿汉式。
- 饿汉式和懒汉式的区别:
- 饿汉式:一开始就创建实例对象
- 优点:线程是安全的。
- 缺点:对象加载时间过长。
- 懒汉式:需要的时候才创建对象的设计模式
- 缺点:应用同步,存在线程安全问题,可以使用多线程锁来解决。
- 优点:延迟对象的创建(延迟加载)
- 饿汉式:一开始就创建实例对象
-
饿汉式实例:在类第一次加载完成之后,就创建实例
// 饿汉式
class Maike{
//1.私有构造器
private Maike(){}
//2. 在类内部创建对象
private static Maike instance = new Maike();
//3. 提供外部访问点
public static Maike getInstance (){
return instance;
}
}
//测试
public class SingleTest{
public static void main(String[ args]){
Maike maike=Maike.test();
}
}
///******************//****************
//饿汉式 枚举方式
/**
* Title:Singleton<br>
* Description:单例模式——枚举方式
*
* @author QiuChangjin
* @date 2018年4月17日
*/
public class Singleton {
public static void main(String[] args) {
Single single = Single.SINGLE;
single.print();
}
enum Single {
SINGLE;
private Single() {
}
public void print() {
System.out.println("hello world");
}
}
}
-
懒汉式实例: 第一次调用时候,才创建实例
//懒汉式
class Maike{
private Maike()
{
}
private static Maike instance=null;
public static Maike getInstance()
{
if (instance==null)
{
instance = new Maike();
}
return instance;
}
}
//测试
public class SingleTest{
public static void main(String[ args]){
Maike maike=Maike.test();
}
}
懒汉式:保证线程安全通过 同步锁 synchronized
1.在getInstance方法上加同步(性能较差)
在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的
//同步方法,效率较低,范围大
public static synchronized Single test(){
if(single==null){
single=new Single();
}
return single;
}
//同步判断
public static synchronized Single test(){
synchronized (Single.class) {
if(single==null){
single = new Single();
}
}
return single;
}
2.双重检查锁定(DCL 双检查锁机制) 推荐
在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
//双重检查 double check:锁的范围小,效率高
public static SingleTon01 newInstance(){
if(single==null){
synchronized (Single.class) {
if(single==null){
single = new Single();
}
}
}
return singleTon;
}
3.静态内部类 推荐
public class Singleton {
/**
* 私有构造方法,禁止在其他类中创建实例
*/
private Singleton(){}
/**
* 一个私有的静态内部类,用于初始化一个静态final实例
*/
private static class SingletonHolder{
private final static Singleton instance=new Singleton();
}
/**
* 获取实例
*/
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
对于单例模式的几种实现方式,知道饿汉式和懒汉式的区别,线程安全,资源加载的时机,还有懒汉式为了实现线程安全的3种方式的细微差别。
一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举方式来实现单例。