您的位置:首页 > 编程语言 > Java开发

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

2018-01-08 19:16 525 查看
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();

}

以上是该博文全部内容。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐