SpringMVC(5):单例模式详解与示例分析(懒汉模式/饿汉模式/静态内部类改造)

2018/1/8

一、单例模式

      一些常用的底层操作,比如数据库连接,获取数据库配置文件的操作等等,我们可以将这些操作提取到一个工具类(ConfigManager.java),并把它设计为单利模式。(23种设计模式之一,最常用的设计模式)在系统运行期间,有且只有一个实例,满足三个关键点:

【1】一个类只有一个实例。因此,只能提供私有的构造器,即保证不能随便创建该类实例。

package com.single;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ConfigManager {
	private static Properties properties;
	//private constructor
	private ConfigManager(){
		String configFile = "database.properties";
		properties = new Properties();
		InputStream is = ConfigManager.class.getClassLoader().getSystemResourceAsStream(configFile);
		try {
			properties.load(is);
			is.close();
		}catch(IOException e){
			e.printStackTrace();
		}
		
		
	}
}

解释:读取配置文件的IO操作放进了 私有的构造器,有效保证IO操作在整个系统运行期间只被执行一次,解决了资源消耗问题。


【2】它必须自行创建这个实例。如下红色字体,定义了静态私有对象ConfigManager:

package com.single;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ConfigManager {
	private static ConfigManager configManager; 
	private static Properties properties;
	//private constructor
	private ConfigManager(){
		String configFile = "database.properties";
		properties = new Properties();
		InputStream is = ConfigManager.class.getClassLoader().getSystemResourceAsStream(configFile);
		try {
			properties.load(is);
			is.close();
		}catch(IOException e){
			e.printStackTrace();
		}
		
		
	}
}


【3】它必须自行向整个系统提供这个实例。这点非常重要!!

外界需要获取并使用这个单例类的实例,但由于这个类的构造器是私有,外界不能new一个,那么必须提供一个静态的共有方法,该方法创建或获取它本身的静态私有对象并返回。

package com.single;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ConfigManager {
	private static ConfigManager configManager; 
	private static Properties properties;
	//private constructor
	private ConfigManager(){
		String configFile = "database.properties";
		properties = new Properties();
		InputStream is = ConfigManager.class.getClassLoader().getSystemResourceAsStream(configFile);
		try {
			properties.load(is);
			is.close();
		}catch(IOException e){
			e.printStackTrace();
		}
	}
	
	//全局访问点
	public static ConfigManager getInstance(){
		if(configManager == null){
			configManager = new ConfigManager();
		}
		return configManager;
	}
}

解释:上面提供了getInstance() 方法来获取 ConfigManager 对象。


二、一个简单示例:

目标:1. 读取 database.properties;2. 获取数据源的配置值(url、driver、password、username);3. CRUD等等JDBC相关的操作。

【1】修改ConfigManager.java 文件:

package com.single;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ConfigManager {
	private static ConfigManager configManager; 
	private static Properties properties;
	//private constructor
	private ConfigManager(){
		String configFile = "database.properties";
		properties = new Properties();
		InputStream is = ConfigManager.class.getClassLoader().getSystemResourceAsStream(configFile);
		try {
			properties.load(is);
			is.close();
		}catch(IOException e){
			e.printStackTrace();
		}
	}
	
	//全局访问点
	public static ConfigManager getInstance(){
		if(configManager == null){
			configManager = new ConfigManager();
		}
		return configManager;
	}
	
	public String getValue(String key){
		//API
		return properties.getProperty(key);
	}
}



【2】新建一个BaseDao类:

package com.single;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.concurrent.locks.AbstractQueuedLongSynchronizer.ConditionObject;

public class BaseDao {
	public static Connection getConnection(){
		Connection connection = null;
		String driver = ConfigManager.getInstance().getValue("driver");
		String url = ConfigManager.getInstance().getValue("url");
		String username = ConfigManager.getInstance().getValue("username");
		String password = ConfigManager.getInstance().getValue("password");
		try{
			Class.forName(driver);
			connection = DriverManager.getConnection(url,username,password);
			
		}catch(Exception e){
			e.getMessage();
		}
		return connection;
	}
	//CRUD 不作补充了
}


解释:(1)上面是最简单的单例模式了;

(2)BaseDao 设计为静态而非单例,因为每个线程都需要单独的Connection。

(3)弊端严重:线程不安全,会出现多个ConfigManager实例


三、懒汉和饿汉模式

解决问题需要先了解单例模式的两种实现方法:懒汉和饿汉模式。


3.1 懒汉模式

   而上例就是采用了懒汉模式--在类加载时没有初始化,需要时调用了getInstance()方法,获取ConfigManager实例。虽保持了延迟加载,但无法在多线程下正常工作。现作以下修改:最简单的就是考虑同步,采用同步synchronize关键字实现。

【1】修改ConfigManager.java 文件的全局访问点:

	//全局访问点
	public static synchronized ConfigManager getInstance(){
		if(configManager == null){
			configManager = new ConfigManager();
		}
		return configManager;
	}
	


解释:(1)上述代码能够在多线程并发环境下很好的工作,同时具备延迟加载特性(lazy loading);

2)遗憾,该方式效率不高,99%的情况下是不需要同步的。

(3)so,推荐饿汉模式。


3.2 饿汉模式

饿汉模式即是:在类加载的时候,就完成了初始化操作,故类加载较慢,但是获取对象的速度很快。并且由于饿汉模式在类初始化时就已经自行实例化,因此肯定不存在线程安全问题。

【1】修改ConfigManager.java 文件:  

package com.hungry;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ConfigManager {
	private static ConfigManager configManager = new ConfigManager(); 
	private static Properties properties;
	//private constructor
	private ConfigManager(){
		String configFile = "database.properties";
		properties = new Properties();
		InputStream is = ConfigManager.class.getClassLoader().getSystemResourceAsStream(configFile);
		try {
			properties.load(is);
			is.close();
		}catch(IOException e){
			e.printStackTrace();
		}
	}
	
	//饿汉模式的全局访问点
	public static ConfigManager getInstance(){
		return configManager;
	}
	
	public String getValue(String key){
		//API
		return properties.getProperty(key);
	}
}

解释:(1) 需要时调用了getInstance()方法,获取ConfigManager实例,而类初始化时就已经自行实例化,这种方式基于classroader机制,有效避免多线程的同步问题。但显然没达到延迟加载特性(lazy loading)效果。



综上,我们希望是饿汉模式+延迟加载特性(lazy loading);


四、静态内部类方式改造

【1】新建 Singleton.java 类:

package com.InnerClass;

public class Singleton {
	private static Singleton singleton;
	private Singleton(){
		//可以添加业务代码--整个系统运行只会执行一次的业务代码操作
	}
	
	//Inner class
	public static class SingletonHelper{
		private static final Singleton INSTANCE = new Singleton();
	}
	
	public static Singleton getInstance(){
		singleton = SingletonHelper.INSTANCE;
		return singleton;
	}
	
	public static Singleton test(){
		return singleton;
	}
}

【2】新建单元测试 SingletonTest.java :

package com.InnerClass;

import org.apache.log4j.Logger;
import org.junit.Test;

public class SingletonTest {
	private Logger log = Logger.getLogger(SingletonTest.class.getName());
	@Test
	public void test() {
		log.info("Singleton.test()--->"+Singleton.test());
		log.info("Singleton.getInstance()--->"+Singleton.getInstance());
	}

}

【3】输出结果:

2018-01-08 19:10:45,667 [com.InnerClass.SingletonTest]-[INFO] Singleton.test()--->null
2018-01-08 19:10:45,729 [com.InnerClass.SingletonTest]-[INFO] Singleton.getInstance()--->com.InnerClass.Singleton@6f75e721

解释:(1)getInstance():
singleton = SingletonHelper.INSTANCE; 

调用了该方法才能获取得了实例;延迟效果达到!!

(2)同时也满足了饿汉模式,在类加载时就已经完成了初始化!!内部类,静态!!

public static class SingletonHelper{
private static final Singleton INSTANCE = new Singleton();
}


以上是该博文全部内容。


你可能感兴趣的:(项目实战)