在企业级软件开发过程中,为了改善应用程序的性能需要通常使用对象池来控制对象的实例化。例如,在我们每次需要连接一个数据库时都需要创建一个数据库连接,而数据库连接是非常昂贵的对象。所以,为了节省为每次数据库调用都实例化一个数据库连接的资源,我们可以缓存并重用一些创建好的数据库连接对象并通过节省为每次数据库调用都创建一个数据库连接对象的时间和资源来大幅度提高程序性能。

对象池与图书馆很像。图书馆里维护很多书籍。当对某本书的需求增加时,图书馆就会买更多书,否则的话读者们就会一直使用同一本书。在对象池中,首先我们检查对象是否已经被创建且被放到池中,如果已经被放到池中,我们就会得到对象池中缓存的对象;如果没有找到就会创建一个新的对象并放到对象池中以备之后使用。对象池计数广泛地用于大规模应用程序服务,比如企业级Java组件模型(Enterprise Java Beans Servers, EJB),MTS/COM+, 甚至在.NET Framework中.

在这部分,我们将开发一个数据库连接池来缓存数据库连接。创建数据库连接是很昂贵的。在一个典型的Web应用中可能有几千个用户同时访问站点。如果这些用户恰好想要访问数据库的动态数据而我们继续为每个用户创建一个数据库连接的话,我们将对应用程序的性能带来负面影响。创建一个新的对象要求更多内存。内存分配会降低应用程序性能,最后的结果是Web站点在分发动态内容时变得非常慢,或者到达一个临界值导致站点崩溃。连接池维护一个已创建的对象池,所以需要一个数据库连接的应用程序可以从池中借一个连接并在用完以后还给对象池,而不是创建一个新的数据库连接。一旦数据发送给一个用户,对象的数据库连接就会被收回以备之后使用。

实现对象池

让我们看一个我们的由类图描述的数据库连接池应用。图 5 显示了ObjectPool 类和继承自ObjectPool的DBConnectionSingleton 类。

图 5

ObjectPool 类

我们先贴出ObjectPool 类的代码然后开始讨论:

/*************************************
/* copyright (c) 2012 daniel dong
*
* author:daniel dong
* blog: www.cnblogs.com/danielwise
* email: guofoo@163.com
*
*/ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Timers; namespace ObjectPoolSample
{
public abstract class ObjectPool
{
//Last Checkout time of any object from the pool.
private long lastCheckOut; //Hashtable of the check-out objects.
private static Hashtable locked; //Hashtable of available objects
private static Hashtable unlocked; //Clean-Up interval
internal static long GARBAGE_INTERVAL = 90 * 1000; //90 seconds
static ObjectPool()
{
locked = Hashtable.Synchronized(new Hashtable());
unlocked = Hashtable.Synchronized(new Hashtable());
} internal ObjectPool()
{
lastCheckOut = DateTime.Now.Ticks; //Create a Time to track the expired objects for cleanup.
Timer aTimer = new Timer();
aTimer.Enabled = true;
aTimer.Interval = GARBAGE_INTERVAL;
aTimer.Elapsed += new ElapsedEventHandler(CollectGarbage);
} protected abstract object Create(); protected abstract bool Validate(object o); protected abstract void Expire(object o); internal object GetObjectFromPool()
{
long now = DateTime.Now.Ticks;
lastCheckOut = now;
object o = null; lock (this)
{
try
{
foreach (DictionaryEntry myEntry in unlocked)
{
o = myEntry.Key;
unlocked.Remove(o);
if (Validate(o))
{
locked.Add(o, now);
return o;
}
else
{
Expire(o);
o = null;
}
}
}
catch (Exception) { }
o = Create();
locked.Add(o, now);
}
return o;
} internal void ReturnObjectToPool(object o)
{
if (o != null)
{
lock (this)
{
locked.Remove(o);
unlocked.Add(o, DateTime.Now.Ticks);
}
}
} private void CollectGarbage(object sender, ElapsedEventArgs ea)
{
lock (this)
{
object o;
long now = DateTime.Now.Ticks;
IDictionaryEnumerator e = unlocked.GetEnumerator(); try
{
while (e.MoveNext())
{
o = e.Key; if ((now - (long)unlocked[o]) > GARBAGE_INTERVAL)
{
unlocked.Remove(o);
Expire(o);
o = null;
}
}
}
catch (Exception) { }
}
}
}
}

ObjectPool 类有两个重要的方法; GetObjectFromPool(), 从对象池中获取一个对象, ReturnObjectToPool(), 把对象还给对象池。我们以两个哈希表实现对象池,一个称为locked, 另一个称为unlocked. locked 哈希表包含所有正在使用的对象而unlocked 哈希表包含了所有未被使用且可随时使用的对象。ObjectPool 还有三个三个必须重载的方法:Create(), Validate() 和 Expire(), 它们必须由继承类实现。

总而言之,ObjectPool 类中有三个关键部分:

使用GetObjectFromPool() 来从对象池中获取一个对象,当需要向对象池中添加一个对象时必须使用锁,由于这个过程locked 和 unlocked 哈希表的内容会发生变化而我们不想在这个过程中发生冲突。

使用ReturnObjectToPool() 来把一个对象返回给对象池,同样需要使用锁,理由同上。

使用CollectGarbage() 从对象池中清除过期对象,在这个方法中我们遍历unlocked哈希表以便从对象池中找到并移除过期对象。这个过程中unlocked哈希表的内容可能会发生改变所以我们需要使用锁来保证这一过程是原子操作。

GetObjectFromPool() 方法中,我们遍历unlocked哈希表来获取第一个可用对象。获得了以后使用Validate() 方法去验证指定对象。基于不同的缓存对象类型,Validate()方法的实现也可能有很大不同。例如,如果对象是一个数据库连接,那么继承对象池的类就需要实现Validate()方法来检查数据库连接是打开的还是关闭的。如果对象池对象验证通过了,我们从unlocked哈希表中移除这个对象并把它放到locked哈希表中。locked 哈希表中的对象表示正在使用的对象。如果验证失败,我们就使用Expired()方法把对象注销。Expire()方法也需要通过继承类实现并根据不同的缓存对象类型而有不同的实现形式。还是以一个数据库连接为例,过期对象将关闭数据库连接。如果没有找到一个缓存对象,说明unlocked哈希表是空的,我们使用Create()方法创建一个新对象然后把它放入到locked哈希表中。

ReturnObjectToPool() 方法的实现相对简单一些。我们仅仅需要将对象从locked哈希表中移除并把它放回unlocked哈希表中以备另用。在整个回收过程中,我们不得不考虑应用程序的内存使用情况。对象池与内存使用量成正比。所以,我们缓存的对象越多,就需要使用更多内存。为了控制内存使用量,我们应该周期性地对池中的对象进行垃圾回收处理。这可以通过对池中每个对象加一个超时周期来实现。如果在超时时间内一个缓存对象没有被使用,那么它将会被作为垃圾回收。结果就是对象池的内存使用量将很大程序上取决于系统负载。

CollectGarbage() 方法用来处理对象池的垃圾回收。这个方法由ObjectPool构造函数中初始化的一个Timer委托进行调用。在我们的例子中,我们通过GARBAGE_COLLECT 常量将垃圾回收时间间隔定位90秒。

我们还没有实现任何数据库连接相关的代码,所以我们假设ObjectPool 类可以用于对.NET Framework 中的所有类型进行缓存。

DBConnectionSingleton 类

DBConnectionSingleton 类实现了一个数据库连接对象池。这个类的主要目的是为继承自ObjectPool 类的特定数据库连接实现Create(), Validate() 和 Expire()方法。这个类也提供BorrowDBConnection() 和 ReturnDBConnection() 方法来从对象池中借出/返还数据库连接。

DBConnectionSignletion 类的完整代码片段如下:

/*************************************
/* copyright (c) 2012 daniel dong
*
* author:daniel dong
* blog: www.cnblogs.com/danielwise
* email: guofoo@163.com
*
*/ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data; namespace ObjectPoolSample
{
public sealed class DBConnectionSingletion : ObjectPool
{
private DBConnectionSingletion() { } public static readonly DBConnectionSingletion Instance =
new DBConnectionSingletion(); private static string connectionString =
@"server=(local);Trusted Connection=yes;database=northwind"; public static string ConnectionString
{
get
{
return connectionString;
}
set
{
connectionString = value;
}
} protected override object Create()
{
SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
return conn;
} protected override bool Validate(object o)
{
try
{
SqlConnection conn = (SqlConnection)o;
return !conn.State.Equals(ConnectionState.Closed);
}
catch (SqlException)
{
return false;
}
} protected override void Expire(object o)
{
try
{
SqlConnection conn = (SqlConnection)o;
conn.Close();
}
catch (SqlException) { }
} public SqlConnection BorrowDBConnection()
{
try
{
return (SqlConnection)base.GetObjectFromPool();
}
catch (Exception e)
{
throw e;
}
} public void ReturnDBConnection(SqlConnection conn)
{
base.ReturnObjectToPool(conn);
}
}
}

由于你正在处理的是SqlConnection对象,所以Expire()方法用来关闭SqlConnection, Create() 方法用来创建SqlConnection 而 Validate() 则用来检查SqlConnection 是打开的还是关闭的。使用DBConnectionSigleton 对象实例可以使整个同步问题对客户端应用程序透明。

为什么要使用单例模式?

Singleton 是一个著名的创建型设计模式,当你需要一个对象仅对应一个实例时通常需要使用它。设计模式一书(ISBN 0-201-70265-7)中对设计单例模式目的定义为保证一个类仅有一个实例,并提供全局唯一的方式来访问它。为了实现一个单例,我们需要一个私有构造函数以便于客户端应用程序无论如何都没法创建一个新对象,使用静态的只读属性来创建单例类的唯一实例。.NET Framework 在JIT 过程中仅当有任何方法使用静态属性时才会将其实例化。如果属性没有被使用,那么也就不会创建实例。更准确地说,仅当有任何类/方法对类的静态成员进行调用时才会构造对应单例类的实例。这个特性称作惰性初始化并把创建对象的过程留给第一次访问实例属性的代码。.NET
Framework 保证共享类型初始化时的类型安全。所以我们不需要担心DBConnectionSingleton对象的线程安全问题,因为在应用程序整个生命周期内金辉创建一个实例。实例静态属性维护DBConnectionSingleton类对象的唯一实例。

使用数据库连接池

现在已经准备好使用数据库连接池了,下面的代码片段显示了如何实例化并使用数据库连接池:

/*************************************
/* copyright (c) 2012 daniel dong
*
* author:daniel dong
* blog: www.cnblogs.com/danielwise
* email: guofoo@163.com
*
*/ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data.SqlClient; namespace ObjectPoolSample
{
class Program
{
static void Main(string[] args)
{
//Initialize the Pool
DBConnectionSingletion pool = DBConnectionSingletion.Instance; //Set the ConnectionString of the DatabaseConnectionPool
ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings["NorthwindConnectionString"];
DBConnectionSingletion.ConnectionString = settings.ConnectionString;
//Borrow the SqlConnection object from the pool
SqlConnection conn = pool.BorrowDBConnection(); //Return the Connection to the pool after using it
pool.ReturnDBConnection(conn); Console.ReadLine();
}
}
}

在上面的例子中,我们通过DBConnectionSingletion 类的实例属性来初始化它的实例。如上面讨论的,我们假设使用单例设计模式可以保证我们有且仅有一个DBConnectionSingletion 对象的实例。我们把ConnectionString 属性设置为本机SQL Server实例上的北风数据库。现在,我们可以使用对象池的BorrowDBConnection() 方法来从对象池借一个数据库连接, 然后通过调用对象池的ReturnDBConnection() 方法来返还数据库连接。如果你真的想看看应用程序池是如何运行的,那么最好的方式就是打开Visual
Studio .NET 中的工程并在调试模式下跟踪上面给出的应用程序代码。

总结

在企业级计算的多线程世界中同步是一个极其重要的概念。它被广泛用于数据库,消息队列以及Web 服务器等闻名应用上。任何开发多线程应用程序的开发人员都必须对他们的同步概念特别清楚。不是为了让每个对象都是线程安全的而导致系统不堪重负,而是应该关注死锁情况并在程序设计之初就解决尽可能多的死锁问题。理解同步带来的性能瓶颈问题同样很重要,因为它将影响应用程序的总体性能。在这一章,除了探讨.NET Framework 中自带的同步特性,我们也开发了两个有用的应用程序:

一个自定义的线程安全包装器。在这个例子中,你学到了如何为你的类库添加原生同步支持并为调用类库的开发人员提供是否使用同步的选项。这将帮助第三方开发人员关注于他们自己的应用程序而不是类库的线程安全问题。

一个数据库连接池。在这个例子中,你开发了可以用于任意相似对象类型的对象池。有了对象池,我们继续开发了一个继承自对象池的数据库连接池。对象池可以用于任意对象。

至此,第三章 使用线程 的内容已经全部介绍完毕,我们学到了如何使用单线程,多线程,如何解决多线程并发问题,多线程并发时可以使用的不同的锁,以及如何使用对象池。这些都为我们理解多线程及其并发提供了很大帮助,希望第三章能给你开发大规模应用程序时提供一些参考抑或帮助。

C# 线程手册 第三章 使用线程 实现一个数据库连接池(实战篇)的更多相关文章

  1. 《JAVA多线程编程核心技术》 笔记:第三章:线程间通信

    一. 等待/通知机制:wait()和notify()1.1.使用的原因:1.2 具体实现:wait()和notify()1.2.1 方法wait():1.2.2 方法notify():1.2.3 wa ...

  2. 深入Java线程管理(三):线程同步

    一. 引入同步: 有一个很经典的案例,即银行取款问题.我们可以先看下银行取款的基本流程: 1)用户输入账户.密码,系统判断用户的账户.密码是否匹配. 2)用户输入取款金额. 3)系统判断账户金额是否大 ...

  3. Linux/Unix系统编程手册 第三章:系统编程概念

    本章介绍系统编程的基础概念和一些后续章节用到的函数及头文件,并说明了可移植性问题. 系统调用是受控的内核入口,通过系统调用,进程可以请求内核以自己的名义去执行某些动作,比如创建子进程,执行I/O操作, ...

  4. 转:Git 求生手册 - 第三章分支工作

    from:http://newbranch.cn/zhi-zuo-fen-zhi-lai-gong-zuo-git-gh-pages-branching/ 来自:片段 实战 说了这么一大堆分支的东西. ...

  5. 《Java并发编程实战》第三章 对象的共享 读书笔记

    一.可见性 什么是可见性? Java线程安全须要防止某个线程正在使用对象状态而还有一个线程在同一时候改动该状态,并且须要确保当一个线程改动了对象的状态后,其它线程能够看到发生的状态变化. 后者就是可见 ...

  6. 《Entity Framework 6 Recipes》中文翻译系列 (19) -----第三章 查询之使用位操作和多属性连接(join)

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-16  过滤中使用位操作 问题 你想在查询的过滤条件中使用位操作. 解决方案 假 ...

  7. 第三章 C#循环与方法

    第一节1-For循环入门 语法: for(条件表达式) { 执行语句 } 练习: 第三章作业1.写一个程序打印100到200的值;2.写一个程序从10打印到1:3.写一个程序打印10到30之间的所有偶 ...

  8. paip.提升性能----数据库连接池以及线程池以及对象池

    paip.提升性能----数据库连接池以及线程池以及对象池 目录:数据库连接池c3po,线程池ExecutorService:Jakartacommons-pool对象池 作者Attilax  艾龙, ...

  9. JS复习:第三章&第四章

    第三章 一.把一个值转换成字符串的两种方法: 1.使用每个值都有的toString( )方法.这个方法唯一要做的就是返回相应值的字符串表现.例如: var age = 11 ; var ageAsSt ...

随机推荐

  1. socket.io框架

    socket.io框架 一.问题背景 目前公司在互联网产品上需要程序与前端部分要进行一个实时交互,在进行一定程度上的选型后,决定使用socket.io框架进行一个实践,算是公司的一个新的 尝试,也算是 ...

  2. node的cookie-parser和express-session

    let express = require('express'); let cookieParser = require('cookie-parser'); let expressSession = ...

  3. [转帖]Lifetime Support Stages for Your Oracle Products

    Lifetime Support Stages for Your Oracle Products https://www.oracle.com/support/lifetime-support/ Pr ...

  4. [转帖]Linux的进程线程及调度

    Linux的进程线程及调度 本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10393707.html 本文为宋宝华<Linux的进程 ...

  5. RAID 磁盘阵列说明

    Copy From wiki RAID档次 最少硬盘 最大容错 可用容量 读取性能 写入性能 安全性 目的 应用产业 单一硬盘 (引用) 0 1 1 1 无     JBOD 1 0 n 1 1 无( ...

  6. 好文章之——PHP系列(一)

    注:最近实习的公司是一家做电商企业,后台主要是php开发,好久不怎么接触php的我看了几篇相关文章,提高下对它的认识与理解,发现里面的学习思路还是非常好的,当然也会重新拾一下基础知识啦! 其实自己心中 ...

  7. Metaspace 之一:Metaspace整体介绍(永久代被替换原因、元空间特点、元空间内存查看分析方法)

    回顾 根据JVM内存区域的划分,简单的画了下方的这个示意图.区域主要分为两大块,一块是堆区(Heap),我们所New出的对象都会在堆区进行分配,在C语言中的malloc所分配的方法就是从Heap区获取 ...

  8. scipy线性模块liner(linalg)

    #liner import numpy as np from scipy import linalg as lg arr=np.array([[1,1],[0,1]]) matr=np.mat('[1 ...

  9. python删除数组元素导致跳过元素

    复现的情况大概可以写成这样 abc = [1, 2, 2, 3, 4] print abc for index, i in enumerate(abc): if i == 2: del abc[ind ...

  10. python自然语言处理函数库nltk从入门到精通

    1. 关于Python安装的补充 若在ubuntu系统中同时安装了Python2和python3,则输入python或python2命令打开python2.x版本的控制台:输入python3命令打开p ...