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

对象池与图书馆很像。图书馆里维护很多书籍。当对某本书的需求增加时,图书馆就会买更多书,否则的话读者们就会一直使用同一本书。在对象池中,首先我们检查对象是否已经被创建且被放到池中,如果已经被放到池中,我们就会得到对象池中缓存的对象;如果没有找到就会创建一个新的对象并放到对象池中以备之后使用。对象池计数广泛地用于大规模应用程序服务,比如企业级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 objectsprivate static Hashtable unlocked;//Clean-Up intervalinternal static long GARBAGE_INTERVAL = 90 * 1000; //90 secondsstatic 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 PoolDBConnectionSingletion pool = DBConnectionSingletion.Instance;//Set the ConnectionString of the DatabaseConnectionPoolConnectionStringSettings settings = ConfigurationManager.ConnectionStrings["NorthwindConnectionString"];DBConnectionSingletion.ConnectionString = settings.ConnectionString;//Borrow the SqlConnection object from the poolSqlConnection conn = pool.BorrowDBConnection();//Return the Connection to the pool after using itpool.ReturnDBConnection(conn);Console.ReadLine();}}
}

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

总结

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

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

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

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

转载于:https://www.cnblogs.com/zhaoshujie/p/9594768.html

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

  1. C# 线程手册 第三章 使用线程

    概要 在之前章节,我们已经讨论过线程在开发多用户应用程序时扮演的重要角色.我们已经使用线程来解决一些重要的问题,比如让多个用户或者客户端在同一时间访问同一个资源.然而,在学习过程中我们忽略了一个问题, ...

  2. C# 线程手册 第三章 使用线程 Monitor.TryEnter()

    Monitor 类的TryEnter() 方法在尝试获取一个对象上的显式锁方面和 Enter() 方法类似.然而,它不像Enter()方法那样会阻塞执行.如果线程成功进入关键区域那么TryEnter( ...

  3. Java7并发编程指南——第三章:线程同步辅助类

    Java7并发编程指南--第三章:线程同步辅助类 @(并发和IO流) Java7并发编程指南第三章线程同步辅助类 思维导图 项目代码 思维导图 项目代码 GitHub:Java7Concurrency ...

  4. C# 线程手册 第四章 线程设计原则 对等线程模型

    我们将要描述的下一个线程模型是对等线程模型.在这个线程模型中,每个线程都会从合适的源接收它自己的输入并对应地处理.这个模型在图4中做了描述. 图 4 在上面的图片中,UI 线程将根据键盘和鼠标的输入进 ...

  5. MySQL 5.6 手册 第三章 目录

    Chapter 3 Tutorial 第三章 辅导教程 Table of Contents 目录   3.1 Connecting to and Disconnecting from the Serv ...

  6. 线程学习(三):线程的互斥

    线程互斥 生产者与消费者模型 为什么需要线程同步和互斥 线程互斥 没有线程互斥会怎么样 mutex(互斥量) 互斥量接口 互斥量初始化有两种方式 互斥量销毁 互斥量的加锁和解锁 死锁 互斥锁使用步骤 ...

  7. Android 渗透测试学习手册 第三章 Android 应用的逆向和审计

    第三章 Android 应用的逆向和审计 作者:Aditya Gupta 译者:飞龙 协议:CC BY-NC-SA 4.0 在本章中,我们将查看 Android 应用程序或.apk文件,并了解其不同的 ...

  8. 网络协议和Netty(4):大白话说三次握手及用一个数据库连接解释三次握手

    前言: 想起了第一次听说"三次握手"概念的时候.那时候刚到北京,朋友傲娇的跟我讲,我花了两天我终于搞懂了三次握手.当时对网络一无所知的我,还以为是啥新的礼仪方式,心中感叹,帝都人民 ...

  9. Java基础 - 坦克大战(第三章,线程基础与线程使用)

    文章目录 本章内容 - 多线程处理 绘制敌方坦克 线程基础 线程相关概念 程序(program) 进程 什么是线程 其他相关概念 单线程 多线程 并发 并行 获取当前电脑处理器(cpu)个数 Java ...

最新文章

  1. Visual与IMU多传感器融合的定位方案
  2. css3波浪纹路_使用CSS3实现的波浪分隔线
  3. 使用nodejs代码在SAP C4C里创建Individual customer
  4. 解决 fprintd-0.1-19.git04fd09cfa.el6 crash问题
  5. c#完美截断字符串(中文+非中文)
  6. 一次面试总结(记录)
  7. 担心你的文章图片被盗?试试用python加水印
  8. 塞班、libc.lib、系统错误-1、KErrNotFound、内嵌sis、embedded sis
  9. MixConv: Mixed Depthwise Convolutional Kernels
  10. Node.js-威富通H5微信支付
  11. java 8 排序_java8——排序
  12. MR:二:什么是MR混合现实技术?
  13. Dx11--用dx11绘制棱台,并用键盘和鼠标进行旋转缩放操作
  14. AtomicInteger类下的incrementAndGet
  15. vue实现实时直播 摄像头实现实时直播 dplayer+flv flv.js
  16. STM32显示软件取模图片
  17. hooks之useState和setEffect
  18. sdkman使用教程
  19. 宅男福利 用Python爬取美女图片
  20. 51单片机学习笔记-2数码管显示

热门文章

  1. CentOS7安装ngnix
  2. cloudflare免费证书_Cloudflare免费CDN加速和免费SSL证书服务
  3. python 排列组合算法_基于python快速实现排列组合算法
  4. 门口养了只孔雀的那家公司,办公室里还有 7、8 种小动物
  5. 《模拟飞行入坑(一)P3D目录文件介绍》
  6. “吃饭行情”吃过饭了,汤能剩点么?
  7. 隆重推荐:疯狂实验史
  8. javascript alter弹出网页提示框
  9. 数据装载命令Load
  10. U 盘启动盘创建工具 Rufus在Linux 上不能使用?这里有 6 个替代品