Singleton模式可以作为一种编程技术,让我们先从理论上说代码
单例模式三个关键点:
1)、某个类仅仅能有一个实例
2)、该类必须自行创建这个实例
3)、该类必须自行向整个系统提供这个实例
应用场景:
1)、window的任务管理器就是非常典型的单例模式,你肯定不能同一时候打开两个任务管理器
2)、数据库连接池技术一般採用的都是单例模式。由于数据库连接是一种数据库资源。系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的
效率损耗。这样的效率上的损耗还是很昂贵的,用单例模式来维护。就能够大大减少这样的损耗。
3)、我们在进行开发时对于配置文件的读取一般也是採用单例模式,由于配置文件里的内容是全局共享的资源。
4)、多线程的线程池设计一般也要考虑单例模式。线程池能方便对池中线程的控制。
5)、站点的统计类信息。一般也是採用单例模式,否则难以同步控制。比如统计我的博客的訪问量。
6)、我们开发应用程序的日志功能也是採用的单例模式,由于我们仅仅能有一个实例去追加日志信息,否则不好控制。
单例模式的几类写法:
1)饿汉式模式
怎么理解呢,饿了吗。所以我们做饭要很着急,这里也就是说。当类被载入的时候该类就已经将自己的实例创建出来了。
这也是空间换取时间的典型应用,怎么说呢? 我们在类载入的时候就实例化了这个对象。占用了内存空间,可是我们在用到这个对象的时候就不用去实例化了,
直接拿去用就能够了。也就节省可时间,这也就是空间换取时间。
ps:记得第一份工作(还在大三的时候)面试的时候面试官就让我举出我做过的项目中时间换取空间和空间换取时间的典型应用,给我问懵了。
代码:
package chc.singleton; /** * 饿汉式单例模式类 * @author haicheng.cao * @time 2014.09.02 22:40 */ public class EagerSingleton { //类载入的时候就创建了自身的实例对象--饿汉式(空间换取时间) public static EagerSingleton eagerSingleton=new EagerSingleton(); /** * 显示的私有构造方法,防止其它类创建本类实例 */ private EagerSingleton(){ } /** * 静态工厂方法,其它类通过调用该方法来获取本类的实例对象 */ public static EagerSingleton getEagerSingleton(){ return eagerSingleton; } }
2)懒汉式模式
这个怎么理解呢?懒人吗,举个样例。一个人立即要去面试的时候才開始写简历,就是说对象实例要用的时候才去创建。在这里也就是说在类载入的时候并没有创
建本类的实例对象,而是在其它类在第一次调用的时候才去创建。
这也是典型的时间换取空间的应用,就是嘛,类载入的时候没有创建对象。节省了内存。也就是节省了空间,调用的时候须要推断一下这个类的实例对象是否存
在,浪费了时间,这也就是时间换取空间。
代码:
package chc.singleton; /** * 懒汉式单例模式类 * @author haicheng.cao * @time 2014.09.02 23:05 */ public class LazySingleton { //类载入的时候并没有创建自身实例化对象 public static LazySingleton lazySingleton=null; /** * 显示的私有构造方法。防止其它类创建本类实例 */ private LazySingleton(){ } /** * 静态工厂方法,其它类通过调用该方法来获取本类的实例对象 */ public static synchronized LazySingleton getLazySingleton(){ //第一次被调用的时候创建自身实例对象 if(lazySingleton==null){ lazySingleton=new LazySingleton(); } return lazySingleton; } }
注意:我们给getEasySingleton()方法加了同步keyword,能够保证线程安全。可是这样做比較影响程序的性能,我们能不能改善一下呢?看以下的:
3)双重检查加锁
双重检查加锁机制的意思就是:我们在调用getEasySingleton()方法的时候不同步,进入方法内我们推断一下实例对象是否存在。假设不存在我们在进入同步代
码块。这是第一重检查,进入同步块后再进行推断,推断实例是否存在,假设不存在再创建这个对象的实例。这就是第二重检查。
这样。就仅仅有第一次调用的时候
运行了一次同步代码块,其余的时候就不须要同步了,提升了程序的性能。
代码:
package chc.singleton; /** * 双重检查加锁。针对懒汉式提升性能 * @author haicheng.cao * @time 2014.09.02 22:40 */ public class TwoCheck { private volatile static TwoCheck twoCheck = null; private TwoCheck(){ } public static TwoCheck getInstance(){ //先检查实例是否存在,假设不存在才进入以下的同步块 if(twoCheck == null){ //同步块,线程安全的创建实例 synchronized (TwoCheck.class) { //再次检查实例是否存在,假设不存在才真正的创建实例 if(twoCheck == null){ twoCheck = new TwoCheck(); } } } return twoCheck; } }
-------------------------------------以下续写与2014.09.03 21:15-----------------------------------------------------
昨天在写这个东西的时候一直在纠结一个问题,如果有一部分全局共享的变量,我们能够通过在类中声明静态属性。然后通过静态方法来初始化声明的那些属性。
然后这些静态的变量在不论什么一个类中都能够被调用了。就像以下这种代码:
package chc.statics; public class StaticDemo { public String logPath=null; public void init(){ logPath="c://log.txt"; } }
这种代码不是全然能够替代单例的功能吗?
今天上班问了下领导。给我解释的非常清楚,类中声明静态变量的方式的确能够实现单例模式的功能。可是,上面代码那种方式你须要在项目启动的时候调用一下
StaticDemo类的init()方法。单例模式就是全然由自身去维护自己,不须要借助外力。
另一种情况:就是当有些全局属性是动态变化的时候,那么对于静态变量的方式就须要程序不断的去操作该类的动态属性,而静态类能够灵活的自己控制。解除
了代码的耦合。
package chc.singleton; import java.io.*; public class Singleton { public static File file=null; public static long lastModified; private static Singleton s=null; private Singleton(){ lastModified=file.lastModified(); } public synchronized static Singleton getSingleton() { //假设变量lastModified的值与文件最后一次被改动的时间值不同的话,又一次实例化一下 if(s==null && lastModified!=file.lastModified() ){ s=new Singleton(); } return s; } }
这个样例就非常直观了。类中的lastModified的值是动态的,假设用静态代码块去维护的话。程序在每一次改动这个文件的时候都要调用一次静态代码
块又一次初始化一下这个变量,单例中却能够自己灵活的进行维护,不须要别的类辅助。
-------------------------------------以下续写与2014.11.03 20:40-----------------------------------------------------
饿汉式存在着占用资源的问题。懒汉式存在着线程安全的问题,以下看一个巧妙的写法,将懒汉式与饿汉式的长处集成在了一起。攻克了懒汉式与饿汉式的弊端。
package hirain; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * 即实现了延迟载入,又线程安全 * @author haicheng.cao */ public class AppConfig5 { //静态内部类在第一次使用的时候被装载 private static class AppConfig5Holder{ private static AppConfig5 instance = new AppConfig5(); } /** * 定义一个方法来为client提供AppConfig类的实例 * @return 一个AppConfig的实例 */ public static AppConfig5 getInstance(){ return AppConfig5Holder.instance; } /** * 私有化构造方法 */ private AppConfig5(){ //调用读取配置文件的方法 readConfig(); } private String id; private String name; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * 读取配置文件,把配置文件里的内容读出来设置到属性上 */ private void readConfig(){ Properties p = new Properties(); InputStream in = null; try { in = new BufferedInputStream (new FileInputStream("AppConfig.properties")); p.load(in); //把配置文件里的内容读出来设置到属性上 this.id = p.getProperty("id"); this.name = p.getProperty("name"); } catch (IOException e) { System.out.println("装载配置文件出错了,详细堆栈信息例如以下:"); e.printStackTrace(); }finally{ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } }
关键点:静态内部类的使用,静态内部类的静态变量仅仅有在静态内部类被使用的时候才会载入中。
版权声明:本文博主原创文章,博客,未经同意不得转载。