代码改变世界

C# 线程手册 第三章 使用线程 创建线程安全的包装器(实战篇)

2012-02-15 22:15  DanielWise  阅读(3162)  评论(7编辑  收藏  举报

在这部分我们将看两个大的例子。首先,我们将看一下创建线程安全的包装器的例子,然后看一下数据库连接池例子。

实现自己的线程安全包装器

实现自己的线程安全包装器主要基于你可能不想让类库中的每个类都是线程安全的,而使用同步也会带来性能问题的事实。你可能想要为开发人员提供一个是否使用一个同步类的选择。由于开发人员既不想发生死锁也不想发生由于在一个单线程环境中使用线程安全类而导致的性能问题,他们可能更倾向于为类库中相同的类使用内建同步包装器而不是为每个类分别实现一个。System.Collections命名空间中的ArrayList和Hashtable集合类早就有了这个特性。你可以决定在初始化一个Hashtable的时候决定是使用一个线程安全的还是非线程安全的。你可以通过像下面代码那样调用Hashtable类的共享Synchronized()方法来实现一个线程安全Hashtable.

Hashtable h = Hashtable.Synchronized(new Hashtable());

为开发人员提供这样的一个选项是非常好的。在这个例子中,我们尝试开发一个类以及这个类的同步包装器。我们实现一个图书集合类库并使用图4的UML形式来表示。

UML

图 4

 

程序很简单,但是使用固有同步支持的概念非常重要。通过为我们的类库添加固有同步支持,我们将允许开发人员为同一个类选择同步实现和非同步实现。例如,不需要使用同步方法的开发人员可以像下面这样实例化一个对象:

BookLib acc = new BookLib();

而需要在多线程环境中使用线程安全包装的开发人员可以如下实现:

BookLib acc = new BookLib();
acc = acc.Synchronized();

下面是带同步包装器的IBookCollection 源码:

/*************************************
/* 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;

namespace BookLibrary
{
    interface IBookCollection
    {
        void Clear();
        void Add(Book n);
        Book GetBook(string ISBN);
        bool IsSynchronized { get; }
        object SyncRoot { get; }
    }
}

下面是BookLib源码:

/*************************************
/* 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.Threading;

namespace BookLibrary
{
    class BookLib : IBookCollection
    {
        internal Hashtable bk = new Hashtable(10);

        public virtual void Clear()
        {
            this.bk.Clear();
        }

        public virtual void Add(Book b)
        {
            Console.WriteLine("Adding Book for ThreadID: "
                + Thread.CurrentThread.GetHashCode());
            Thread.Sleep(2000);
            bk.Add(b.ISBN, b);
        }

        public virtual Book GetBook(string ISBN)
        {
            Console.WriteLine("Getting Book for ThreadID: "
                + Thread.CurrentThread.GetHashCode());
            return (Book)bk[ISBN];
        }

        public virtual bool IsSynchronized
        {
            get { return false; }
        }

        public virtual object SyncRoot
        {
            get { return this; }
        }

        public BookLib Synchronized()
        {
            return Synchronized(this);
        }

        public static BookLib Synchronized(BookLib bc)
        {
            if (bc == null)
            {
                throw new ArgumentException("bc");
            }

            if (bc.GetType() == typeof(SyncBookLib))
            {
                throw new InvalidOperationException(
                    "BookLib reference is already synchronized.");
            }

            return new SyncBookLib(bc);
        }

        public static IBookCollection Synchronized(IBookCollection acc)
        {
            if (acc == null)
            {
                throw new ArgumentException("acc");
            }

            if (acc.GetType() == typeof(SyncBookLib))
            {
                throw new InvalidOperationException(
                    "BookLib reference is already synchronized.");
            }

            return new SyncBookLib(acc);
        }
    }
}

下面是SyncBookLib源码:

/*************************************
/* 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;

namespace BookLibrary
{
    sealed class SyncBookLib : BookLib
    {
        private object syncRoot;
        private object booklib;

        internal SyncBookLib(IBookCollection acc)
        {
            booklib = acc;
            syncRoot = acc.SyncRoot;
        }

        public override void Clear()
        {
            lock (syncRoot)
            {
                base.Clear();
            }
        }

        public override void Add(Book b)
        {
            lock (syncRoot)
            {
                base.Add(b);
            }
        }

        public override Book GetBook(string ISBN)
        {
            lock (syncRoot)
            {
                return base.GetBook(ISBN);
            }
        }

        public override object SyncRoot
        {
            get
            {
                return syncRoot;
            }
        }
    }
}

在上面的代码中,我们首先声明了一个接口IBookCollection, 它有下面的一些处理书籍集合的方法和属性:

  Clear() - 清除书籍集合的方法

  Add() - 向集合中添加一本书的方法

  GetBook() - 从集合中获取一本书的方法

  IsSynchronized() - 用来检查集合是否为同步的只读属性

  SyncRoot() - 获取集合同步根的只读属性

下面我们定义一个用来表示集合中的一本书的Book类。例如,集合可能是一个图书馆或者一家书店,但是Book类所表达的意义都是一样的。

BookLib类实现了IBookCollection接口。所以BookLib必须实现IBookCollection接口的所有方法和属性。我们定义了一个名为bk的Hashtable来作为书籍集合。Book对象的键值是ISBN码。在Add()方法中,我们向哈希表中添加了一本书。在GetBook()方法中,如果提供了书籍的ISBN码,我们将会得到书籍对象。

现在我们必须处理同步问题。在Synchronized()方法中,我们创建了一个SyncBookLib类型的对象并返回一个它的引用。SyncBookLib是BookLib类的同步版本。SyncBookLib继承自BookLib类,也就继承了BookLib类已经实现的所有属性和方法。SyncBookLib和BookLib类的不同之处在于SyncBookLib类中我们使用lock锁定了代码的关键部分。例如,Clear(), GetBook() 和 Add() 方法的实现中都用到了锁,所以它们都是线程安全的。而BookLib类的所有方法中都没有锁。

在测试类中,如果我们传递任何命令行参数那么就可以创建一个同步的BookLib实例。如果不传递任何参数则传递一个非线程安全的BookLib实例。当运行程序时。线程安全的BookLib实现和非线程安全的实现区别是非常明显的。在线程安全版本中,在任何时间只能有一个线程访问。所以其他两个线程不得不等第一个线程处理完。而非线程安全的版本中所有线程都允许同时访问BookLib对象实例。

带命令行参数的BookLib(线程安全)的输出结果如下:

threadsafe

不带命令行参数的BookLib(非线程安全)输出结果如下:

non-threadsafe

 

下一篇介绍一个数据库连接池…