单例模式可以当做一种编程的技巧,咱们先说理论再说代码
单例模式三个关键点:
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;
}
}
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();
}
/**
* 定义一个方法来为客户端提供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();
}
}
}
}
关键点:静态内部类的使用,静态内部类的静态变量只有在静态内部类被使用的时候才会加载。