//----引入必要的命名空间
using System.IO;
using System.Drawing.Imaging;

//----代码部分----//

        private byte[] photo;//公用缓冲区
        public string SourFilePath;//源图片文件路径
        public string ObjFilePath;//目标图片路径

        public int FileToStream()//文件到流的转换
        {
            Image img = new Bitmap(SourFilePath);
            MemoryStream stream = new MemoryStream();
            img.Save(stream, ImageFormat.Bmp);
            BinaryReader br = new BinaryReader(stream);
            photo = stream.ToArray();
            stream.Close();
            return 0;
        }

        public Image ShowPic()//根据流显图
        {
            byte[] bytes = photo;
            MemoryStream ms = new MemoryStream(bytes);
            ms.Position = 0;
            Image img = Image.FromStream(ms);
            ms.Close();
            return img;
        }

        public int StreamToFile()//反向转换
        {
            byte[] bytes = photo;
            FileStream fs = new FileStream(ObjFilePath, FileMode.Create, FileAccess.Write);
            fs.Write(bytes, 0, bytes.Length);
            fs.Flush();
            fs.Close();
            return 0;
        }
posted @ 2009-07-31 16:43 awp110 阅读(47) 评论(0) 编辑
posted @ 2009-07-31 16:30 awp110 阅读(200) 评论(0) 编辑

对于稍微有点经验的.NET开发人员来说,倘若被问及如何保持线程同步,我想很多人都能说好好几种。在众多的线程同步的可选方式中,加锁无疑是最为常用的。如果仅仅是基于方法级别的线程同步,使用System.Runtime.CompilerServices.MethodImplAttribute无疑是最为简洁的一种方式。MethodImplAttribute可以用于instance method,也可以用于static method。当在某个方法上标注了MethodImplAttribute,并指定MethodImplOptions.Synchronized参数,可以确保在不同线程中运行的该方式以同步的方式运行。我们几天来讨论MethodImplAttribute(MethodImplOptions.Synchronized)和lock的关系。

一、提出结论

在进行讨论之前,我先提出下面3个结论:

1、[MethodImplAttribute(MethodImplOptions.Synchronized)]仍然采用加锁的机制实现线程的同步。

2、如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到instance method,相当于对当前实例加锁。

3、如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到static method,相当于当前类型加锁

二、基于instance method的线程同步

为了验证我们上面提出的结论,我作了一个小小的例子。在一个console application中定义了一个class:SyncHelper,其中定义了一个方法Execute。打印出方法执行的时间,并休眠当前线程模拟一个耗时的操作:

class SyncHelper
{
    public void Execute()
    {
        Console.WriteLine("Excute at {0}", DateTime.Now);
        Thread.Sleep(5000);
    }
}

在入口Main方法中,创建SyncHelper对象,通过一个System.Threading.Timer对象实现每隔1s调用该对象的Execute方法:

class Program
{
    static void Main(string[] args)
    {
        SyncHelper helper = new SyncHelper();
        Timer timer = new Timer(
        delegate
        {
            helper.Execute();
        }, null, 0, 1000);

        Console.Read();

    }
}

 

 

 

 

 

 

 

 

 

由于Timer对象采用异步的方式进行调用,所以虽然Execute方法的执行时间是5s,但是该方法仍然是每隔1s被执行一次。这一点从最终执行的结果可以看出:

image

为了让同一个SyncHelper对象的Execute方法同步执行,我们在Execute方法上添加了如下一个MethodImplAttribute:

[MethodImpl(MethodImplOptions.Synchronized)]
public void Execute()
{
    Console.WriteLine("Excute at {0}", DateTime.Now);
    Thread.Sleep(5000);
}

 

 

 

从如下的输出结果我们可以看出Execute方法是以同步的方式执行的,因为两次执行的间隔正式Execute方法执行的时间:

image

在一开始我们提出的结论中,我们提到“如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到instance method,相当于对当前实例加锁”。说得直白一点:[MethodImplAttribute(MethodImplOptions.Synchronized)] = lock(this)。我们可以通过下面的实验验证这一点。为此,在SyncHelper中定义了一个方法LockMyself。在此方法中对自身加锁,并持续5s中,并答应加锁和解锁的时间。

public void LockMyself()
{
   lock (this)
    {
        Console.WriteLine("Lock myself at {0}", DateTime.Now);
        Thread.Sleep(5000);
        Console.WriteLine("Unlock myself at {0}", DateTime.Now);
    }
}

我们在Main()中以异步的方式(通过创建新的线程的方式)调用该方法:

static void Main(string[] args)
{
    SyncHelper helper = new SyncHelper();

    Thread thread = new Thread(
        delegate()
        {           

             helper.LockMyself();

        });
    thread.Start();
    Timer timer = new Timer(
    delegate
    {
        helper.Execute();
    }, null, 0, 1000);

    Console.Read();
}

 

 

 

 

 

 

结合我们的第二个结论想想最终的输出会是如何。由于LockMyself方法是在另一个线程中执行,我们可以简单讲该方法的执行和Execute的第一个次执行看作是同时的。但是MethodImplAttribute(MethodImplOptions.Synchronized)]果真是通过lock(this)的方式实现的话,Execute必须在等待LockMyself方法执行结束将对自身的锁释放后才能得以执行。也就是说LockMyself和第一次Execute方法的执行应该相差5s。而输出的结果证实了这点:

image

三、基于static method的线程同步

讨论完再instance method上添加MethodImplAttribute(MethodImplOptions.Synchronized)]的情况,我们相同的方式来讨论倘若一样的MethodImplAttribute被应用到static方法,又会使怎样的结果。

我们先将Execute方法上的MethodImplAttribute注释掉,并将其改为static方法:

//[MethodImpl(MethodImplOptions.Synchronized)]
public static void Execute()
{
    Console.WriteLine("Excute at {0}", DateTime.Now);
    Thread.Sleep(5000);
}

 

 

 

 

 

 

 

 

在Main方法中,通过Timer调用该static方法:

static void Main(string[] args)
{
    Timer timer = new Timer(
    delegate
    {
        SyncHelper.Execute();
    }, null, 0, 1000);

    Console.Read();
}

毫无疑问,Execute方法将以1s的间隔异步地执行,最终的输出结果如下:

image

然后我们将对[MethodImpl(MethodImplOptions.Synchronized)]的注释取消:

[MethodImpl(MethodImplOptions.Synchronized)]
public static void Execute()
{
    Console.WriteLine("Excute at {0}", DateTime.Now);
    Thread.Sleep(5000);
}

最终的输出结果证实了Execute将会按照我们期望的那样以同步的方式执行,执行的间隔正是方法执行的时间:

image

我们回顾一下第三个结论:“如果[MethodImplAttribute(MethodImplOptions.Synchronized)]被应用到static method,相当于当前类型加锁”。为了验证这个结论,在SyncHelper中添加了一个新的static方法:LockType。该方法对SyncHelper tpye加锁,并持续5s中,在加锁和解锁是打印出当前时间:

public static void LockType()
{
    lock (typeof(SyncHelper))
    {
        Console.WriteLine("Lock SyncHelper type at {0}", DateTime.Now);
        Thread.Sleep(5000);
        Console.WriteLine("Unlock SyncHelper type at {0}", DateTime.Now);
    }
}

在Main中,像验证instance method一样,创建新的线程执行LockType方法:

static void Main(string[] args)
{
    Thread thread = new Thread(
        delegate()
        {
            SyncHelper.LockType();
        });
    thread.Start();

    Timer timer = new Timer(
    delegate
    {
        SyncHelper.Execute();
    }, null, 0, 1000);

    Console.Read();
}

如果基于static method的[MethodImplAttribute(MethodImplOptions.Synchronized)]是通过对Type进行加锁实现。那么通过Timer轮询的第一个Execute方法需要在LockType方法执行完成将对SyncHelper type的锁释放后才能执行。所以如果上述的结论成立,将会有下面的输出:

image

四、总结

对于加锁来说,锁的粒度的选择显得至关重要。在不同的场景中需要选择不同粒度的锁。如果选择错误往往会对性能造成很到的影响,严重时还会引起死锁。就拿[MethodImplAttribute(MethodImplOptions.Synchronized)]来说,如果开发人员对它的实现机制不了解,很有可能使它lock(this)或者lock(typeof(…))并存,造成方法得不到及时地执行。

最后说一句题外话,因为字符串驻留机制的存在,切忌对string进行加锁。

posted @ 2009-07-31 15:18 awp110 阅读(88) 评论(0) 编辑

lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。 此语句的形式如下:

Object thisLock = new Object();
lock (thisLock)
{
    // Critical code section.
}

有关更多信息,请参见 线程同步(C# 编程指南)

lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

线程处理(C# 编程指南) 这节讨论了线程处理。

lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit

通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。 常见的结构 lock (this)lock (typeof (MyType))lock ("myLock") 违反此准则:

  • 如果实例可以被公共访问,将出现 lock (this) 问题。

  • 如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。

  • 由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock(“myLock”) 问题。

最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。

下面演示在 C# 中使用未锁定的线程的简单示例。

//using System.Threading;

class ThreadTest
{
    public void RunMe()
    {
        Console.WriteLine("RunMe called");
    }

    static void Main()
    {
        ThreadTest b = new ThreadTest();
        Thread t = new Thread(b.RunMe);
        t.Start();
    }
}
// Output: RunMe called

下例使用线程和 lock。 只要 lock 语句存在,语句块就是临界区并且 balance 永远不会是负数。

// using System.Threading;

class Account
{
    private Object thisLock = new Object();
    int balance;

    Random r = new Random();

    public Account(int initial)
    {
        balance = initial;
    }

    int Withdraw(int amount)
    {

        // This condition will never be true unless the lock statement
        // is commented out:
        if (balance < 0)
        {
            throw new Exception("Negative Balance");
        }

        // Comment out the next line to see the effect of leaving out 
        // the lock keyword:
        lock (thisLock)
        {
            if (balance >= amount)
            {
                Console.WriteLine("Balance before Withdrawal :  " + balance);
                Console.WriteLine("Amount to Withdraw        : -" + amount);
                balance = balance - amount;
                Console.WriteLine("Balance after Withdrawal  :  " + balance);
                return amount;
            }
            else
            {
                return 0; // transaction rejected
            }
        }
    }

    public void DoTransactions()
    {
        for (int i = 0; i < 100; i++)
        {
            Withdraw(r.Next(1, 100));
        }
    }
}

class Test
{
    static void Main()
    {
        Thread[] threads = new Thread[10];
        Account acc = new Account(1000);
        for (int i = 0; i < 10; i++)
        {
            Thread t = new Thread(new ThreadStart(acc.DoTransactions));
            threads[i] = t;
        }
        for (int i = 0; i < 10; i++)
        {
            threads[i].Start();
        }
    }
}
posted @ 2009-07-31 14:56 awp110 阅读(41) 评论(0) 编辑

使用C# lock同时访问共享数据

经常碰到同时需要对某个数据进行操作,或者对某个文件进行读写操作,对于这些操作我们以前往往不能很好的进行处理,自从C#语言中引入了lock这个关键字,以上问题就比较容易予以解决了,下面就是一段简单的代码。

public class AccessControl()
{
    
private static object privateObjectLock = new object();

    
public static AccessResult()
    {
    
lock(privateObjectLock)
    {
         
//数据操作语句
    }
    }
}
posted @ 2009-07-31 14:53 awp110 阅读(50) 评论(0) 编辑

此前本想在网上找找实现Asp.Net的IP地址屏蔽功能的文章来参考,但是一搜索“IP 屏蔽 asp.net”,出现的全都是:

image

image这些都是对我此前写的《细说Asp.net的IP地址屏蔽功能设计》一文的无情转载,不仅不保留出处、作者,而且连标题都没一个与我原文相同的~~

这篇文章写的是程序设计部分,并没有编程实现屏蔽功能,搜索引擎上翻了几页也没找到个正经写这方面的文章,无奈只好自己来研究实现,并写下此文的续篇了,倒是没什么难度。

 

本文将介绍通过实现IHttpModule接口,进行判断和屏蔽IP地址的方法。

(HttoModule的基础知识可以参阅这里:http://www.tracefact.net/Asp-Net/Introduction-to-Http-Module.aspx

阅读前请先参阅《细说Asp.net的IP地址屏蔽功能设计》一文,本文将使用这篇文章中提出的思路,并将使用文中创建的数据库、实体类。

 

首先,新建一个类,名为IPFilter,继承自IHttpModule接口:

image 

实现IHttpModule接口,并为context对象的AcquireRequestState事件添加事件处理:

image

(因为我们要用到Session,而在早于AcquireRequestState的事件中Session还未被初始化。参考于:http://www.cnblogs.com/junqilian/archive/2008/03/07/1095454.html

事件处理方法:

image

这里的主要功能是从Session中读取用户IP,再从缓存中读取IP地址屏蔽列表,遍历IP地址屏蔽数据,判断是否应当屏蔽当前IP,如果判断为屏蔽,就关闭输出,让客户端无法访问。

黄色高亮区域:这里是在判断Session是否为空,其原因是不能保证执行到这里时Session总是存在的,我曾在有异步访问的页面中遇到过这里报错的情况,所以这样处理比较稳妥。

绿色高亮区域:这是在《细说Asp.net的IP地址屏蔽功能设计》一文中提供的实体类方法。

蓝色高亮区域:辅助方法,其代码见下文:

image

 

此方法用于获取IP地址。

image

此方法用于从数据库中读取有效的IP地址屏蔽数据,并将其装入缓存。

缓存时间设置为固定3分钟。

至此,过滤类就实现了。

 

接下来还需要向Web.Config文件中注册此HttpModule处理程序:

image

这样就全部完成了。

 

屏蔽测试:

image

添加IP屏蔽数据后3分钟内(依据缓存时间设置),被屏蔽的访问者继续浏览网站就会出现这样的提示了。

 

源代码:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Net;

 

namespace MySite

{

    public class IPFilter : IHttpModule

    {

        void context_AcquireRequestState(object sender, EventArgs e)

        {

            var c = (sender as HttpApplication).Context;

            if (c.Session == null) return;

            IPAddress ip = null;

            if (c.Session["IP"] == null)

            {

                c.Session["IP"] = ip = IPAddress.Parse(获取客户端IP地址(c));

            }

            else ip = c.Session["IP"] as IPAddress;

            if (c.Cache["IPFilter"] == null) 更新IP屏蔽列表缓存();

            var l = c.Cache["IPFilter"] as List<IP地址屏蔽>;

            foreach (var f in l)

            {

                if (f.检测是否被屏蔽(ip))

                {

                    c.Response.Close();

                    break;

                }

            }

        }

 

        void 更新IP屏蔽列表缓存()

        {

            using (var c = new DatabaseEntities())

            {

                var iplist = c.IP地址屏蔽.Where(f => f.过期时间 > DateTime.Now).ToList();

                HttpContext.Current.Cache.Insert("IPFilter",

                    iplist,

                    null,

                    DateTime.Now.AddMinutes(3),

                    System.Web.Caching.Cache.NoSlidingExpiration,

                    System.Web.Caching.CacheItemPriority.AboveNormal,

                    null);

            }

        }

 

        /// <summary>

        /// 获得当前页面客户端的IP

        /// </summary>

        public static string 获取客户端IP地址(HttpContext c)

        {

            string result = String.Empty;

 

            result = c.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];

            if (string.IsNullOrEmpty(result))

            {

                result = c.Request.ServerVariables["REMOTE_ADDR"];

            }

 

            if (string.IsNullOrEmpty(result))

            {

                result = c.Request.UserHostAddress;

            }

 

            if (string.IsNullOrEmpty(result))

            {

                return "0.0.0.0";

            }

 

            return result;

        }

 

        #region IHttpModule 成员

 

        public void Dispose()

        {

 

        }

 

        public void Init(HttpApplication context)

        {

            context.AcquireRequestState += new EventHandler(context_AcquireRequestState);

        }

 

        #endregion

    }

}

 

 

 

总结来说没什么难度,但是因为这段代码在每次请求中都会执行一次,所以对性能要求很高,如果你有什么优化方面的建议,欢迎提出。

posted @ 2009-07-31 14:43 awp110 阅读(111) 评论(0) 编辑
开始-所有程序-Microsoft .NET Framework SDK v2.0-Tools-MSIL 反汇编程序
posted @ 2009-07-31 11:31 awp110 阅读(24) 评论(0) 编辑
posted @ 2009-07-31 11:23 awp110 阅读(64) 评论(0) 编辑
posted @ 2009-07-31 11:22 awp110 阅读(30) 评论(0) 编辑
posted @ 2009-07-31 11:20 awp110 阅读(46) 评论(0) 编辑
posted @ 2009-07-31 11:19 awp110 阅读(56) 评论(0) 编辑

http://www.cnblogs.com/haozi0804/archive/2009/07/31/1535524.html
第一章
简单的开始

本章我们从一个简单的项目开始,一个基于NHibernate2.1数据库O/R Mapping工具的简单编程实例,来说明即使我们使用了O/R Mapping工具仍然有可能会把程序写成没有层次结构和杂乱无章。不是说这样的看上去简单的结构不是好结构,正如很多程序员说的一样,能解决问题的结构就是好的结构。问题的关键是怎么解决问题的?项目有多大、业务领域有多复杂、项目团队有多少人等等这些因素都会决定着软件架构本身。作为编写围绕企业应用的行业软件,项目的成功的关键是我们在规定的时间、预算范围内,交付能够满足用户需求的软件产品。

只是在项目的前期多考虑一下项目的维护问题、升级问题和项目在下一个项目的重用性问题等等,一开始就着手一个简单和“好”的框架会在某些时候带来意想不到的收获,同时为了这个收获并没有付出太多额外的工作,只是你的团队成员和你一起知道如何运用这些技术——领域模型(Domain Model)。

让我们一步一步的来,先从简单的开始。这里先说明例子所使用的开发环境:

开发工具:Microsoft Visual Studio 2008 Version 9.0.21022.8 RTM

Microsoft .NET Framework Version 3.5 SP1

Microsoft Visual Studio 2008 Version 9.0.21022.8 RTM

Microsoft .NET Framework Version 3.5 SP1

开发语言:C#

操作系统:Microsoft XP professional Service Pack 2

数据库开发工具:Microsoft SQL Server 2005 Service Pack1

数据映射工具: NHibernate2.1

配置管理工具:Visual SourceSafe 8.0

单元测试工具:TestDriven.NET-2.21.2448_Personal 支持NUnit-2.5

 

1.1       建立数据库表

假定我们有一张关于Customer的数据库表,我的“简单的开始”就是从如何实现这个表的基本业务逻辑开始。

 

 

下面实现创建表的脚本:注意USE [Data]请使用你自己的数据库名称

 

 

USE [Data]

GO

/****** 对象: Table [dbo].[Customer]    脚本日期: 05/31/2009 21:50:49 ******/

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

SET ANSI_PADDING ON

GO

CREATE TABLE [dbo].[Customer](

[CustomerId] [int] NOT NULL,

[Firstname] [varchar](50) COLLATE Chinese_PRC_CI_AS NULL,

[Lastname] [varchar](50) COLLATE Chinese_PRC_CI_AS NULL,

[Gender] [varchar](2) COLLATE Chinese_PRC_CI_AS NULL,

[Address] [varchar](50) COLLATE Chinese_PRC_CI_AS NULL,

[Remark] [varchar](100) COLLATE Chinese_PRC_CI_AS NULL,

CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED

(

[CustomerId] ASC

)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]

) ON [PRIMARY]

 

GO

SET ANSI_PADDING OFF 

  

1.2       创建ASP.NET Web Application项目

VS2008中创建ASP.NET Web Application项目命名为DemoWebApp,把Default.aspx更名为customer.aspx如图所示:

 

 

 

 

 

 

 

 

 

  DEMO程序我们使用NHibernate2.2为映射工具,关于NHibernate2.2的使用请参考相关网络资源如:NHibernate之旅系列文章导航

 

1.3      创建NHibernate2.2领域模型映射文件

我添加NHibernate2.2 Model文件Customer.cs和它的映射文件Customer.hbm.xml

Customer.cs代码如下: 

 

using System;

using System.Collections.Generic;

using System.Text;

 

namespace DemoWebApp

{

    public class Customer

    {

        public virtual int CustomerId { get; set; }

        public virtual string Firstname { get; set; }

        public virtual string Lastname { get; set; }

        public virtual string Gender { get; set; }

        public virtual string Address { get; set; }

public virtual string Remark { get; set; }

    }

}

 

Customer.hbm.xml代码如下:

 

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">

         <class name="DemoWebApp.Customer, DemoWebApp" table="Customer">

                   <id name="CustomerId" type="Int32" unsaved-value="null">

                            <column name="CustomerId" length="4" sql-type="int" not-null="true" />

                            <generator class="assigned" />

                   </id>

                   <property name="Firstname" type="String">

                            <column name="Firstname" length="50" sql-type="varchar" not-null="false"/>

                   </property>

<property name="Lastname" type="String">

<column name="Lastname" length="50" sql-type="varchar" not-null="false"/>

                   </property>

                   <property name="Gender" type="String">

                            <column name="Gender" length="2" sql-type="char" not-null="false"/>

                   </property>

                   <property name="Address" type="String">

                            <column name="Address" length="50" sql-type="varchar" not-null="false"/>

                   </property>              

    <property name="Remark" type="String">

      <column name="Remark" length="100" sql-type="varchar" not-null="false"/>

    </property>

         </class>

</hibernate-mapping>

 

  这两个文件是实现Model对象与表之间的关系映射所必需的,通过这两个文件我们才能完成对象到数据库表的数据持久化。

 

1.4       实现Customer模型的增加、查询、修改和删除基本业务

接下来我们实现关于Customer的增加、查询、修改和删除简单的业务操作,基于Web的程序我们是如何完成的这样的功能实现的。如图,我们在WEB界面上增加操作按钮,然后在按钮的点击事件里实现相应的业务代码。

 

我们需要给项目添加NHibernate库文件的引用。这样才能调用NHibernate类库文件提供的API访问,如图:

 

 

  同时记得在Web.Config配置NHibernate的项目,以确保程序能够使用NHibernate类库。

 

 <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">

    <session-factory>

      <property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property>

      <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>

      <property name="connection.connection_string">Data Source=MES9;Initial Catalog=Data;Persist Security Info=True;User ID=sa;Password=sa</property>

    </session-factory>

 </hibernate-configuration>

 

  然后在Web页面的Page_Load事件里初始化NHibernate的会话,才能根据Session去调用持久层提供的API操作数据。

 

using System.Linq;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Xml.Linq;

using System.Text;

 

using NHibernate;

using NHibernate.Cfg;

using NHibernate.Criterion;

 

namespace DemoWebApp

{

    public partial class _Default : System.Web.UI.Page

    {

private Configuration _config;

        private ISessionFactory _factory;

        private ISession _session;

        private Customer _customer;

 

        protected void Page_Load(object sender, EventArgs e)

        {

            _config = new Configuration().AddAssembly("DemoWebApp");

            _factory = _config.BuildSessionFactory();

            _session = _factory.OpenSession(); //获得一个NHibernate当前会话

            

        }

  

1.4.1创建一个业务对象并持久化到数据库

现在我们来看看如何增加一个客户数据吧。我们首先创建一个Customer对象,然后把文本框里的值赋给Customer对应的属性,最后向NHibernate Session 提交该Customer对象完成Customer对象的数据库提交工作。同时提交过程中我们启动了Session的事务管理,已确保提交出现异常时事务会回滚等。 

 

 protected void bt_Add_Click(object sender, EventArgs e)

        {

            _customer = new Customer();

            _customer.Firstname = this.TextBox1.Text.Trim(); ;

            _customer.Gender = this.TextBox2.Text.Trim();

            _customer.Address = this.TextBox3.Text.Trim();

            _customer.CustomerId = 1000;

 

            ITransaction tran = _session.BeginTransaction();

            try

            {

                _session.Save(_customer);

                tran.Commit();

 

            }

            catch

            {

                tran.Rollback();

                if (_session.Contains(_customer))

                {

                    _session.Evict(_customer);

                }

 

            }

            this.tb_name.Text = _customer.CustomerId.ToString();

 

        }

 

  1.4.2根据标识查询一个持久化对象

protected void Button3_Click(object sender, EventArgs e)

{

_customer = (Customer)_session.Get(typeof(Customer), Convert.ToInt32(tb_name.Text.Trim()));

 if (_customer != null)

{

this.TextBox1.Text = _customer.Firstname;

this.TextBox2.Text = _customer.Gender;

this.TextBox3.Text = _customer.Address;

this.TextBox5.Text = _customer.Remark;

}

}

 1.4.3修改一个持久化对象

protected void Button1_Click(object sender, EventArgs e)

        {

            _customer = _session.Get<Customer>(Convert.ToInt32(tb_name.Text.Trim()));

            _customer.Firstname = this.TextBox1.Text.Trim(); ;

            _customer.Gender = this.TextBox2.Text.Trim();

            _customer.Address = this.TextBox3.Text.Trim();

            _customer.Remark = this.TextBox5.Text.Trim();

            ITransaction tran = _session.BeginTransaction();

            try

            {

                _session.SaveOrUpdate(_customer);

                tran.Commit();

            }

            catch

            {

                tran.Rollback();

                if (_session.Contains(_customer))

                {

                    _session.Evict(_customer);

                }

            }

        }

                                                                                           

1.4.4删除一个持久化对象

protected void Button2_Click(object sender, EventArgs e)

        {

            _customer = (Customer)_session.Get(typeof(Customer), Convert.ToInt32(tb_name.Text.Trim()));

            ITransaction tran = _session.BeginTransaction();

            try

            {

                _session.Delete(_customer);

                tran.Commit(); 

            }

            catch

            {

                tran.Rollback();

                if (_session.Contains(_customer))

                {

                    _session.Evict(_customer);

                }       

            }

        }

 

1.5       结束语 

 上例我们可以看出即使使用面向对象的持久化工具,我们面向领域编程,如果没有良好的代码组织和层次结构,我们同样可以写出领域模型与业务逻辑及对象映射都揉杂在界面层的事件中,这样的软件结构在面向简单业务和快速的实现上对于许多程序员来说是容易入手和理解的。可是当这样的代码需要升级维护以满足趋于复杂的业务时,我们发现系统的多么的脆弱和难于理解。在我的项目生涯中,听到程序员抱怨最多的就是与其读懂并修改别人的代码还不如自己重新写一个(当然这还与代码缺乏良好的命名习惯、编写组织及注释等等有关)。于是我们的项目中会不断的重复这样的现象,另一个程序员接受别人的功能模块时,总是打定主意重新开始,但是他往往忽略了当别人维护他的代码时也会发出同样的感叹。 

 

posted @ 2009-07-31 11:17 awp110 阅读(54) 评论(0) 编辑

多线程:

Type text here

 

    class ActiveDate
    
{
        
private string _CusID;
        
private string _Visitor;
        
private int _ShopNum;

        
private int _flag = 0;

        
public int Flag
        
{
            
get return _flag; }
            
set { _flag = value; }
        }


        
public string CusID
        
{
            
get return _CusID; }
            
set { _CusID = value; }
        }


        
public string Visitor
        
{
            
get return _Visitor; }
            
set { _Visitor = value; }
        }


        
public int ShopNum
        
{
            
get return _ShopNum; }
            
set { _ShopNum = value; }
        }



        
public void GetCustomerVisitor()
        
{
            _Visitor 
= DAL.CRM.Common.Customer.GetCustomerVisitor(_CusID);
            
lock (this)
            
{
                _flag
++;
            }

        }


        
public void GetCustomerShopNums()
        
{
            _ShopNum 
= Convert.ToInt32(DAL.CRM.Common.Customer.GetCustomerShopNum(_CusID));
            
lock (this)
            
{
                _flag
++;
            }

        }

    }



            Module.CRM.Customer.CustomerActiveDate date 
= new Module.CRM.Customer.CustomerActiveDate();

            ActiveDate ad 
= new ActiveDate();
            ad.CusID 
= customerID;

            Thread tr1 
= new Thread(new ThreadStart(ad.GetCustomerVisitor));
            Thread tr2 
= new Thread(new ThreadStart(ad.GetCustomerShopNums));

            tr1.Start();
            tr2.Start();

            
while (true)
            
{
                
if (ad.Flag == 2)
                
{
                    date.Visitor 
= ad.Visitor;
                    date.ShopNum 
= ad.ShopNum;
                    tr1.Abort();
                    tr2.Abort();
                    
return date;
                }

            }
委托:
delegate string myDelegate(String Name);
            myDelegate d1 
= new myDelegate(DAL.CRM.Common.Customer.GetCustomerVisitor);
            myDelegate d2 
= new myDelegate(DAL.CRM.Common.Customer.GetCustomerShopNum);

            IAsyncResult i1 
= d1.BeginInvoke(customerID, nullnull);


            Module.CRM.Customer.CustomerActiveDate date 
= new Module.CRM.Customer.CustomerActiveDate();

            IAsyncResult i2 
= d2.BeginInvoke(customerID, nullnull);

            
bool _flag = false;

            
while (!_flag)
            
{
                _flag 
= i1.IsCompleted && i2.IsCompleted;
            }


            date.Visitor 
= d1.EndInvoke(i1);
            date.ShopNum 
= Convert.ToInt32(d2.EndInvoke(i2));

            
return date;
posted @ 2009-07-31 10:32 awp110 阅读(33) 评论(0) 编辑

using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
using System.Threading;
using System.IO;
using System.Runtime.InteropServices;
using System;
using System.Runtime.Remoting.Messaging;

namespace Test
{
    
class Program
    {

        
delegate void mydelegate();

        
private void smork()
        {
            
//方法1
            AsyncCallback mycallback = new AsyncCallback(tellyou);//(回调函数)
            mydelegate mdg = new mydelegate(move);
            IAsyncResult result 
= mdg.BeginInvoke(mycallback, null);
            result.AsyncWaitHandle.WaitOne();
//等待异步完成
            Console.WriteLine("异步调用后");

            
//方法2
            
//AsyncCallback mycallback = new AsyncCallback(tellyou);
            
//mydelegate mdg = new mydelegate(move);
            
//IAsyncResult result = mdg.BeginInvoke(mycallback, null);
            
//while (!result.IsCompleted)
            
//{
            
//    //Thread.Sleep(50);//不要与要有区别
            
//}


            
// 方法3(没有回调函数)
            
// mydelegate mdg = new mydelegate(move);
            
// IAsyncResult result = mdg.BeginInvoke(null, null);
            
// while (!result.IsCompleted)
            
// {
            
//     //Thread.Sleep(60);
            
// }
            
// mdg.EndInvoke(result); //停止调用


            
//方法4(没有回调函数)
            
//mydelegate mdg = new mydelegate(move);
            
//IAsyncResult result = mdg.BeginInvoke(null, null);
            
//result.AsyncWaitHandle.WaitOne();
            
//mdg.EndInvoke(result); //停止调用 


        }
        
        
public void move()
        {
            
for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(
"异步任务执行");
                Thread.Sleep(
1000);
            }
        }
        
        
public void tellyou(IAsyncResult result)//回调函数
        {
            AsyncResult asyncResult 
= result as AsyncResult;

            mydelegate mydelegate 
= asyncResult.AsyncDelegate as mydelegate;
            mydelegate.EndInvoke(result);

            
if (result.IsCompleted)
            {
                Console.WriteLine(
"异步任务完成");
            }
        }

        
static void Main(string[] args)
        {
            Program pr 
= new Program();
            Console.WriteLine(
"开始执行..");
            pr.smork();
            Thread.Sleep(
1000);//注释掉结果有点不同
            Console.WriteLine("我是主线程");
            Console.ReadLine();
        }
    }
}
posted @ 2009-07-31 10:30 awp110 阅读(13) 评论(0) 编辑
有关多线程的一些技术问题:

1、  何时使用多线程?

2、  线程如何同步?

3、  线程之间如何通讯?

4、  进程之间如何通讯?



先来回答第一个问题,线程实际主要应用于四个主要领域,当然各个领域之间不是绝对孤立的,他们有可能是重叠的,但是每个程序应该都可以归于某个领域:

1、  offloading time-consuming task。由辅助线程来执行耗时计算,而使GUI有更好的反应。我想这应该是我们考虑使用线程最多的一种情况吧。

2、  Scalability。服务器软件最常考虑的问题,在程序中产生多个线程,每个线程做一份小的工作,使每个CPU都忙碌,使CPU(一般是多个)有最佳的使用率,达到负载的均衡,这比较复杂,我想以后再讨论这个问题。

3、  Fair-share resource allocation。当你向一个负荷沉重的服务器发出请求,多少时间才能获得服务。一个服务器不能同时为太多的请求服务,必须有一个请求的最大个数,而且有时候对某些请求要优先处理,这是线程优先级干的活了。

4、  Simulations。线程用于仿真测试。

我把主要的目光放在第一个领域,因为它正是我想要的。第二和第三个领域比较有意思,但是目前不在我的研究时间表中。



线程的同步机制:

1、  Event
用 事件(Event)来同步线程是最具弹性的了。一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状态。事件又分两种类型:手动重置事件 和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设 置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态。所以用自动重置事件来同步两个线程比较理想。MFC中对应的类为 CEvent.。CEvent的构造函数默认创建一个自动重置的事件,而且处于未激发状态。共有三个函数来改变事件的状 态:SetEvent,ResetEvent和PulseEvent。用事件来同步线程是一种比较理想的做法,但在实际的使用过程中要注意的是,对自动重 置事件调用SetEvent和PulseEvent有可能会引起死锁,必须小心。

2、  Critical Section
使用 临界区域的第一个忠告就是不要长时间锁住一份资源。这里的长时间是相对的,视不同程序而定。对一些控制软件来说,可能是数毫秒,但是对另外一些程序来说, 可以长达数分钟。但进入临界区后必须尽快地离开,释放资源。如果不释放的话,会如何?答案是不会怎样。如果是主线程(GUI线程)要进入一个没有被释放的 临界区,呵呵,程序就会挂了!临界区域的一个缺点就是:Critical Section不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放临界资源,系统无法获知,而且没有办法释放该临 界资源。这个缺点在互斥器(Mutex)中得到了弥补。Critical Section在MFC中的相应实现类是CcriticalSection。CcriticalSection::Lock()进入临界 区,CcriticalSection::UnLock()离开临界区。

3、  Mutex
互斥器的功能和临界区域很相似。区别 是:Mutex所花费的时间比Critical Section多的多,但是Mutex是核心对象(Event、Semaphore也是),可以跨进程使用,而且等待一个被锁住的Mutex可以设定 TIMEOUT,不会像Critical Section那样无法得知临界区域的情况,而一直死等。MFC中的对应类为CMutex。Win32函数有:创建互斥体CreateMutex() ,打开互斥体OpenMutex(),释放互斥体ReleaseMutex()。Mutex的拥有权并非属于那个产生它的线程,而是最后那个对此 Mutex进行等待操作(WaitForSingleObject等等)并且尚未进行ReleaseMutex()操作的线程。线程拥有Mutex就好像 进入Critical Section一样,一次只能有一个线程拥有该Mutex。如果一个拥有Mutex的线程在返回之前没有调用ReleaseMutex(),那么这个 Mutex就被舍弃了,但是当其他线程等待(WaitForSingleObject等)这个Mutex时,仍能返回,并得到一个 WAIT_ABANDONED_0返回值。能够知道一个Mutex被舍弃是Mutex特有的。

4、  Semaphore
信号量 是最具历史的同步机制。信号量是解决producer/consumer问题的关键要素。对应的MFC类是Csemaphore。Win32函数 CreateSemaphore()用来产生信号量。ReleaseSemaphore()用来解除锁定。Semaphore的现值代表的意义是目前可用 的资源数,如果Semaphore的现值为1,表示还有一个锁定动作可以成功。如果现值为5,就表示还有五个锁定动作可以成功。当调用Wait…等函数要 求锁定,如果Semaphore现值不为0,Wait…马上返回,资源数减1。当调用ReleaseSemaphore()资源数加1,当时不会超过初始 设定的资源总数。



线程之间的通讯:
线程常常要将数据传递给另外一个线程。Worker线程可能需要告诉别人说它的工作完成了,GUI线程则可能需要交给Worker线程一件新的工作。
通 过PostThreadMessage(),可以将消息传递给目标线程,当然目标线程必须有消息队列。以消息当作通讯方式,比起标准技术如使用全局变量 等,有很大的好处。如果对象是同一进程中的线程,可以发送自定义消息,传递数据给目标线程,如果是线程在不同的进程中,就涉及进程之间的通讯了。下面将会 讲到。



进程之间的通讯:

当线程分属于不同进程,也就是分驻在不同的地址空间时,它们之间的通讯需要跨越地址空间的边界,便得采取一些与同一进程中不同线程间通讯不同的方法。

1、  Windows 专门定义了一个消息:WM_COPYDATA,用来在线程之间搬移数据,――不管两个线程是否同属于一个进程。同时接受这个消息的线程必须有一个窗口,即 必须是UI线程。WM_COPYDATA必须由SendMessage()来发送,不能由PostMessage()等来发送,这是由待发送数据缓冲区的 生命期决定的,出于安全的需要。

2、  WM_COPYDATA效率上面不是太高,如果要求高效率,可以考虑使用共享内存(Shared Memory)。使用共享内存要做的是:设定一块内存共享区域;使用共享内存;同步处理共享内存。
第 一步:设定一块内存共享区域。首先,CreateFileMapping()产生一个file-mapping核心对象,并指定共享区域的大小。 MapViewOfFile()获得一个指针指向可用的内存。如果是C/S模式,由Server端来产生file-mapping,那么Client端使 用OpenFileMapping(),然后调用MapViewOfFile()。
第二步:使用共享内存。共享内存指针的使用是一件比较麻烦的事,我们需要借助_based属性,允许指针被定义为从某一点开始起算的32位偏移值。
第三步:清理。UnmapViewOfFile()交出由MapViewOfFile()获得的指针,CloseHandle()交出file-mapping核心对象的handle。
第四步:同步处理。可以借助Mutex来进行同步处理。

3、  IPC
1)Anonymous Pipes。Anonymous Pipes只被使用于点对点通讯。当一个进程产生另一个进程时,这是最有用的一种通讯方式。
2)Named Pipes。Named Pipes可以是单向,也可以是双向,并且可以跨越网络,步局限于单机。
3)Mailslots。Mailslots为广播式通讯。Server进程可以产生Mailslots,任何Client进程可以写数据进去,但是只有Server进程可以取数据。
4)OLE Automation。OLE Automation和UDP都是更高阶的机制,允许通讯发生于不同进程间,甚至不同机器间。
5)DDE。DDE动态数据交换,使用于16位Windows,目前这一方式应尽量避免使用。
posted @ 2009-07-31 10:30 awp110 阅读(32) 评论(0) 编辑
前两篇简单介绍了线程同步lock,Monitor,同步事件EventWaitHandler,互斥体Mutex的基本用法,在此基础上,我们对它们用法进行比较,并给出什么时候需要锁什么时候不需要的几点建议。最后,介绍几个FCL中线程安全的类,集合类的锁定方式等,做为对线程同步系列的完善和补充。

      1.几种同步方法的区别

      lock和Monitor是.NET用一个特殊结构实现的,Monitor对象是完全托管的、完全可移植的,并且在操作系统资源要求方面可能更为有效,同步速度较快,但不能跨进程同步。lock(Monitor.Enter和Monitor.Exit方法的封装),主要作用是锁定临界区,使临界区代码只能被获得锁的线程执行。Monitor.Wait和Monitor.Pulse用于线程同步,类似信号操作,个人感觉使用比较复杂,容易造成死锁。

      互斥体Mutex和事件对象EventWaitHandler属于内核对象,利用内核对象进行线程同步,线程必须要在用户模式和内核模式间切换,所以一般效率很低,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步

      互斥体Mutex类似于一个接力棒,拿到接力棒的线程才可以开始跑,当然接力棒一次只属于一个线程(Thread Affinity),如果这个线程不释放接力棒(Mutex.ReleaseMutex),那么没办法,其他所有需要接力棒运行的线程都知道能等着看热闹。

      EventWaitHandle 类允许线程通过发信号互相通信。通常,一个或多个线程在 EventWaitHandle 上阻止,直到一个未阻止的线程调用 Set 方法,以释放一个或多个被阻止的线程。

      2.什么时候需要锁定

      首先要理解锁定是解决竞争条件的,也就是多个线程同时访问某个资源,造成意想不到的结果。比如,最简单的情况是,一个计数器,两个线程 同时加一,后果就是损失了一个计数,但相当频繁的锁定又可能带来性能上的消耗,还有最可怕的情况死锁。那么什么情况下我们需要使用锁,什么情况下不需要 呢?

      1)只有共享资源才需要锁定
      只有可以被多线程访问的共享资源才需要考虑锁定,比如静态变量,再比如某些缓存中的值,而属于线程内部的变量不需要锁定。 

      2)多使用lock,少用Mutex
      如果你一定要使用锁定,请尽量不要使用内核模块的锁定机制,比如.NET的Mutex,Semaphore,AutoResetEvent和 ManuResetEvent,使用这样的机制涉及到了系统在用户模式和内核模式间的切换,性能差很多,但是他们的优点是可以跨进程同步线程,所以应该清 楚的了解到他们的不同和适用范围。

      3)了解你的程序是怎么运行的
      实际上在web开发中大多数逻辑都是在单个线程中展开的,一个请求都会在一个单独的线程中处理,其中的大部分变量都是属于这个线程的,根本没有必要考虑锁定,当然对于ASP.NET中的Application对象中的数据,我们就要考虑加锁了。

      4)把锁定交给数据库
      数 据库除了存储数据之外,还有一个重要的用途就是同步,数据库本身用了一套复杂的机制来保证数据的可靠和一致性,这就为我们节省了很多的精力。保证了数据源 头上的同步,我们多数的精力就可以集中在缓存等其他一些资源的同步访问上了。通常,只有涉及到多个线程修改数据库中同一条记录时,我们才考虑加锁。 

      5)业务逻辑对事务和线程安全的要求
      这 条是最根本的东西,开发完全线程安全的程序是件很费时费力的事情,在电子商务等涉及金融系统的案例中,许多逻辑都必须严格的线程安全,所以我们不得不牺牲 一些性能,和很多的开发时间来做这方面的工作。而一般的应用中,许多情况下虽然程序有竞争的危险,我们还是可以不使用锁定,比如有的时候计数器少一多一, 对结果无伤大雅的情况下,我们就可以不用去管它。

      3.InterLocked类

      Interlocked 类提供了同步对多个线程共享的变量的访问的方法。如果该变量位于共享内存中,则不同进程的线程就可以使用该机制。互锁操作是原子的,即整个操作是不能由相 同变量上的另一个互锁操作所中断的单元。这在抢先多线程操作系统中是很重要的,在这样的操作系统中,线程可以在从某个内存地址加载值之后但是在有机会更改 和存储该值之前被挂起。

      我们来看一个InterLock.Increment()的例子,该方法以原子的形式递增指定变量并存储结果,示例如下:

Increment()方法累加的示例

      输出结果200000000,如果InterLockedTest.Add()方法中用注释掉的语句代替Interlocked.Increment()方法,结果将不可预知,每次执行结果不同。InterLockedTest.Add()方法保证了加1操作的原子性,功能上相当于自动给加操作使用了lock锁。同时我们也注意到InterLockedTest.Add()用时比直接用+号加1要耗时的多,所以说加锁资源损耗还是很明显的。

      另外InterLockedTest类还有几个常用方法,具体用法可以参考MSDN上的介绍。

      4.集合类的同步

      .NET在一些集合类,比如Queue、ArrayList、HashTable和Stack,已经提供了一个供lock使用的对象SyncRoot。用Reflector查看了SyncRoot属性(Stack.SynchRoot略有不同)的源码如下:

SyncRoot属性源码

      这里要特别注意的是MSDN提到:从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。应该使用下面的代码:

Queue使用lock示例

      还有一点需要说明的是,集合类提供了一个是和同步相关的方法Synchronized,该 方法返回一个对应的集合类的wrapper类,该类是线程安全的,因为他的大部分方法都用lock关键字进行了同步处理。如HashTable的 Synchronized返回一个新的线程安全的HashTable实例,代码如下:

Synchronized的使用和理解

      线程同步是一个非常复杂的话题,这里只是根据公司的一个项目把相关的知识整理出来,作为工作的一种总结。这些同步方法的使用场景是怎样的?究竟有哪些细微的差别?还有待于进一步的学习和实践。

posted @ 2009-07-31 10:29 awp110 阅读(133) 评论(0) 编辑
本篇继续介绍WaitHandler类及其子类Mutex,ManualResetEvent,AutoResetEvent的用法。.NET中线程同步的方式多的让人看了眼花缭乱,究竟该怎么去理解呢?其实,我们抛开.NET环境看线程同步,无非是执行两种操作:一是互斥/加锁,目的是保证临界区代码操作的“原子性”;另一种是信号灯操作,目的是保证多个线程按照一定顺序执行,如生产者线程要先于消费者线程执行。.NET中线程同步的类无非是对这两种方式的封装,目的归根结底都可以归结为实现互斥/加锁或者是信号灯这两种方式,只是它们的适用场合有所不。下面我们根据类的层次结构了解WaitHandler及其子类。

      1.WaitHandler

      WaitHandle是Mutex,Semaphore,EventWaitHandler,AutoResetEvent,ManualResetEvent共同的祖先,它封装Win32同步句柄内核对象,也就是说是这些内核对象的托管版本。

      线程可以通过调用WaitHandler实例的方法WaitOne在单个等待句柄上阻止。此外,WaitHandler类重载了静态方法,以等待所有指 定的等待句柄都已收集到信号WaitAll,或者等待某一指定的等待句柄收集到信号WaitAny。这些方法都提供了放弃等待的超时间隔、在进入等待之前 退出同步上下文的机会,并允许其它线程使用同步上下文。WaitHandler是C#中的抽象类,不能实例化。

      2.EventWaitHandler vs. ManualResetEvent vs. AutoResetEvent(同步事件)

      我们先看看两个子类ManualResetEvent和AutoResetEvent在.NET Framework中的实现:

ManualResetEvent 和 AutoResetEvent 的实现


      原来ManualResetEvent和AutoResetEvent都继承自EventWaitHandler,它们的唯一区别就在于父类 EventWaitHandler的构造函数参数EventResetMode不同,这样我们只要弄清了参数EventResetMode值不同 时,EventWaitHandler类控制线程同步的行为有什么不同,两个子类也就清楚了。为了便于描述,我们不去介绍父类的两种模式,而直接介绍子 类。

      ManualResetEvent和AutoResetEvent的共同点:
      1)Set方法将事件状态设置为终止状态,允许一个或多个等待线程继续;Reset方法将事件状态设置为非终止状态,导致线程阻止;WaitOne阻止当前线程,直到当前线程的WaitHandler收到事件信号。
      2)可以通过构造函数的参数值来决定其初始状态,若为true则事件为终止状态从而使线程为非阻塞状态,为false则线程为阻塞状态。
      3)如果某个线程调用WaitOne方法,则当事件状态为终止状态时,该线程会得到信号,继续向下执行。

      ManualResetEvent和AutoResetEvent的不同点:
      1)AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为不发送状态,则其他调用WaitOne的线程只有继续等待,也就是说AutoResetEvent一次只唤醒一个线程;
      2)ManualResetEvent则可以唤醒多个线程,因为当某个线程调用了ManualResetEvent.Set()方法后,其他调用WaitOne的线程获得信号得以继续执行,而ManualResetEvent不会自动将信号置为不发送。
      3)也就是说,除非手工调用了ManualResetEvent.Reset()方法,则ManualResetEvent将一直保持有信号状态,ManualResetEvent也就可以同时唤醒多个线程继续执行。

      示例场景:张三、李四两个好朋友去餐馆吃饭,两个人点了一份宫爆鸡丁,宫爆鸡丁做好需要一段时间,张三、李四不愿傻等,都专心致志的玩 起了手机游戏,心想宫爆鸡丁做好了,服务员肯定会叫我们的。服务员上菜之后,张三李四开始享用美味的饭菜,饭菜吃光了,他们再叫服务员过来买单。我们可以 从这个场景中抽象出来三个线程,张三线程、李四线程和服务员线程,他们之间需要同步:服务员上菜—>张三、李四开始享用宫爆鸡丁—>吃好后叫 服务员过来买单。这个同步用什么呢? ManualResetEvent还是AutoResetEvent?通过上面的分析不难看出,我们应该用 ManualResetEvent进行同步,下面是程序代码:

张三李四吃饭的故事

   
      编译后查看运行结果,符合我们的预期,控制台输出为:
      服务员:厨师在做菜呢,两位稍等...
      张三:等着上菜无聊先玩会手机游戏
      李四:等着上菜无聊先玩会手机游戏
      张三:等着上菜无聊先玩会手机游戏
      李四:等着上菜无聊先玩会手机游戏
      服务员:宫爆鸡丁好了
      张三:开始吃宫爆鸡丁
      李四:开始吃宫爆鸡丁
      张三:宫爆鸡丁吃光了
      李四:宫爆鸡丁吃光了
      服务员:两位请买单

      如果改用AutoResetEvent进行同步呢?会出现什么样的结果?恐怕张三和李四就 要打起来了,一个享用了美味的宫爆鸡丁,另一个到要付账的时候却还在玩游戏。感兴趣的朋友可以把注释的那行代码注释去掉,并把下面一行代码注释掉,运行程 序看会出现怎样的结果。

       3.Mutex(互斥体)

       Mutex和EventWaitHandler有着共同的父类WaitHandler类,它们同步的函数用法也差不多,这里不再赘述。Mutex的突出特点是可以跨应用程序域边界对资源进行独占访问,即可以用于同步不同进程中的线程,这种功能当然这是以牺牲更多的系统资源为代价的。

      这种跨进程同步的一种应用是,限制同一台电脑中同时打开两个相同的程序。具体实现可以参考《用Mutex或进程限制用户在一台电脑上同时打开两个程序》。
posted @ 2009-07-31 10:27 awp110 阅读(115) 评论(0) 编辑
本篇从Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler的类关系图开始,希 望通过本篇的介绍能对常见的线程同步方法有一个整体的认识,而对每种方式的使用细节,适用场合不会过多解释。让我们来看看这几个类的关系图:

 

      1.lock关键字

      lock是C#关键词,它将语句块标记为临界区,确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。方法是获取给定对象的互斥锁,执行语句,然后释放该锁。

      MSDN上给出了使用lock时的注意事项通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则。

      1)如果实例可以被公共访问,将出现 lock (this) 问题。

      2)如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题由于一个类的所有实例都只有一个类型对象(该对象是typeof的返回结果),锁定它,就锁定了该对象的所有实例。微软现在建议不要使用 lock(typeof(MyType)),因为锁定类型对象是个很缓慢的过程,并且类中的其他线程、甚至在同一个应用程序域中运行的其他程序都可以访问 该类型对象,因此,它们就有可能代替您锁定类型对象,完全阻止您的执行,从而导致你自己的代码的挂起。

      3)由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。这个问题和.NET Framework创建字符串的机制有关系,如果两个string变量值都是"myLock",在内存中会指向同一字符串对象。

 

      最佳做法是定义 private 对象来锁定, 或 private static对象变量来保护所有实例所共有的数据。

      我们再来通过IL Dasm看看lock关键字的本质,下面是一段简单的测试代码:

    lock (lockobject)
    
{
        
int i = 5;
    }

      用IL Dasm打开编译后的文件,上面的语句块生成的IL代码为:

      IL_0045:  call       void [mscorlib]System.Threading.Monitor::Enter(object)
      
IL_004a:  nop
      .try
      {
        
IL_004b:  nop
        
IL_004c:  ldc.i4.5
        
IL_004d:  stloc.1
        
IL_004e:  nop
        
IL_004f:  leave.s    IL_0059
      }  // end .try
      finally
      {
        
IL_0051:  ldloc.3
        
IL_0052:  call       void [mscorlib]System.Threading.Monitor::Exit(object)
        
IL_0057:  nop
        
IL_0058:  endfinally
      }  // end handler

      通过上面的代码我们很清楚的看到:lock关键字其实就是对Monitor类的Enter()和Exit()方法的封装,并通过try...catch...finally语句块确保在lock语句块结束后执行Monitor.Exit()方法,释放互斥锁。

      2.Monitor类

      Monitor类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问临界区的能力。当一个线程拥有对象的锁时,其他任何 线程都不能获取该锁。还可以使用 Monitor 来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。

      通过对lock关键字的分析我们知道,lock就是对Monitor的Enter和Exit的一个封装,而且使用起来更简洁,因此Monitor类的Enter()和Exit()方法的组合使用可以用lock关键字替代。

      另外Monitor类还有几个常用的方法:

      TryEnter()能够有效的解决长期死等的问题,如果在一个并发经常发生,而且持续时间长的环境中使用TryEnter,可以有效防止死锁或者长时间 的等待。比如我们可以设置一个等待时间bool gotLock = Monitor.TryEnter(myobject,1000),让当前线程在等待1000秒后根据返回的bool值来决定是否继续下面的操作。

      Wait()释放对象上的锁以便允许其他线程锁定和访问该对象。在其他线程访问对象时,调用线程将等待。脉冲信号用于通知等待线程有关对象状态的更改。

      Pulse(),PulseAll()向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更改,并且锁的所有者准备释放该锁。等待线程被 放置在对象的就绪队列中以便它可以最后接收对象锁。一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。

      注意:Pulse、PulseAll和Wait方法必须从同步的代码块内调用。

      我们假定一种情景:妈妈做蛋糕,小孩有点馋,妈妈每做好一块就要吃掉,妈妈做好一块后,告诉小孩蛋糕已经做好了。下面的例子用Monitor类的Wait和Pulse方法模拟小孩吃蛋糕的情景。

Wait和Pulse的示例

      这个例子的目的是要理解Wait和Pulse如何保证线程同步的,同时要注意Wait(obeject)和Wait(object,int)方法的区别,理解它们的区别很关键的一点是要理解同步的对象包含若干引用,其中包括对当前拥有锁的线程的引用、对就绪队列(包含准备获取锁的线程)的引用和对等待队列(包含等待对象状态更改通知的线程)的引用。

posted @ 2009-07-31 10:26 awp110 阅读(78) 评论(0) 编辑
数据库事务中的基本概念

数据库事务是指,由一个或多个SQL语句组成的工作单元,这个工作单元中的SQL语句相互依赖,如果有一个SQL语句失败,那么整个操作都要撤销。在并发环境中,当多个事务同时访问同一资源时,可能会造成并发问题,此时可以使用数据库系统的事务隔离级别来避免各类并发问题。此外,在应用程序中还可以使用悲观锁和乐观锁来解决丢失更新的并发问题。

数据库事务必须具备ACID的特征(Atomic原子性,Consistency一致性,Isolation隔离性,Durability持久性)
    原子性,指整个数据库事务是不可分割的单元。只有所有的语句执行成功,才算成功。
    一致性,指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
     隔离性,指在并发环境中,当不同的事务同时操作相同的数据时,每个事务都有各自的完整数据空间。
     持久性,指的是只要事务成功结束,对数据库做的更新要永久保存下来.

Transaction和Session的关系,应该注意以下几点
      Transaction的rollback()和Session的close()方法都会抛出HibernateException
     不论事务是否成功,最后都应该关闭Session。
      即使事务中只包含只读操作,也应该在事务成功执行之后提交事务,并且在事务执行失败时撤销事务,因为在提交或撤销事务时,数据库系统会释放占用的资源。

多个事务同时运行时的并发问题
     第一类丢失更新,撤销一个事务时,把其他事务已提交的更新数据覆盖了。
     脏读,一个事务读到另一个事务未提交的更新数据。
     虚读,一个事务读到另一个事务已提交的新插入的数据。
     不可重复读,一个事务读到另一个事务已提交的更新数据。
      第二类更新,一个事务覆盖另一事务已提交的更新数据。

数据库系统锁的基本原理

按照锁定的资源的粒度,锁可以分为以下类型
   数据库级锁: 锁定整个数据库
   表级锁: 锁定一张表
   区域级锁: 锁定数据库的特定区域
   页面级锁: 锁定数据库的特定页面
   见面级锁: 锁定数据库的特定页面
   键值级锁: 锁定数据库表中带有索引的一行数据。
   行级锁: 锁定数据库表中的但行数据(即一条记录)


按照封锁的程度,锁可以分为: 共享锁,独占锁,更新锁

共享锁, 共享锁用于读数据操作,它是非独占的,允许其他事务同时读取其锁定的资源,但不允许其他事务更新数据。共享锁的特征:
加锁条件:当一个事务执行Select操作时,数据库系统就会为其分配一把共享锁,来锁定查询数据。
     解锁条件:在默认情况下,数据读取完毕,共享锁就解除了。
     与其他锁的兼容性:如果数据资源上放置了共享锁,还能再放置共享锁和更新锁。
     并发性能:具有良好的并发性能,当多个事务读相同的数据时,每个事务都会获得共享锁,因此可以同时读锁定的数据。

独占锁,也叫排它锁,适用于修改数据的场合。它锁定的资源,其他事务不能读取也不能更新。独占锁具有以下特征:
    加锁的条件:当一个事务执行insert、update、或delete语句时,数据库就会为SQL所操作的数据使用独占锁。如果数据上有其他锁,那么就能放置独占锁.
    解锁条件:到事务结束时才能被解除
与其他锁的兼容性:独占锁不能和其他锁兼容。通用如果资源上有其他锁,那么也不能放置独占锁。
    并发性能:性能较差,只允许有一个事务访问锁定的数据,如果其他事务也需要访问该数据,就必须等待,直到前一个事务结束,解除了独占锁,其他事务才有机会访问资源。

更新锁,在更新操作的初始化阶段用来锁定可能要被修改的资源,这可以避免使用共享锁造成死锁的现象。更新具有以下特征:
    加锁的条件:当一个事务执行update语句时,数据库系统会先为事务分配一把更新锁。
    解锁条件:读取数据完毕,执行更新操作时,会把更新锁升级为独占锁。
     与其他锁的兼容性:更新锁与共享锁是兼容的。也就是说,一个资源可以同时放置更新锁和共享锁,但是最多只能放置一把更新锁。
    并发性能:允许多个事务同时访问资源。但不允许修改。

死锁及其防止方法
     合理安排表的访问顺序
     使用短事务。将大事务分割为多个小事务执行。应该在处理事务之前就准备好用户必须提供的数据。
     如果对数据的一致性要求不高,可以允许脏度。
     如果可能,错开多个事务访问相同数据资源的时间,以防止锁冲突。
     使用尽可能低的事务级别。





数据库的隔离级别
     尽管数据库系统允许用户在事务中显示的为数据资源加锁,但是首先应该考虑让数据库系统自动管理锁,它会分析事务中的SQL语句,然后自动为SQL语句所操作的数据资源加上合适的锁,而且在锁的数目太多时,数据库系统会自动的进行锁升级,以提供系统性能。

数据库系统提供了四种事务隔离级别供用户选择:

Serializable,串行化。
      一个事务在执行过程中完全看不到事务对数据库所做的更新。当两个事务同时操作数据库中的数据时,如果第一个事务已经在访问该数据,那么第二个事务只能停下来等待。
Repeatable Read:可重复读
      一个事务在执行过程中可以看到其他事务已经提交的新插入的数据。但是不能看到其他事务对已有数据做的跟新。
Read Commited:读已提交数据
      一个事务在执行过程中可以看到其他事务已经提交的新插入的数据。可以看到其他对已有数据进行的更新。
Read UnCommited:读取未提交
     一个事务可以看到,其他事务没有提交新插入的数据。而且更新操作的记录也能看到。

//: 在hibernate.cfg.xml中配置
hibernate.connection.isolation=2

在应用程序中才用悲观锁和乐观锁

悲观锁与乐观锁的概念
悲观锁,在应用程序中显示的为数据资源加锁。可以防止丢失更新和不可重复读问题。但是会影响性能。
乐观锁,假设当前的事务操作的数据,不会有其他事务同时访问该数据资源,完全依靠数据库系统自动管理锁。因此可能会出现并发问题。

利用数据库系统的独占锁来实现悲观锁
悲观锁的两种实现方式
应用中显示的指定采用数据库系统的独占锁来锁定数据资源。
在数据库表中增加一个表明记录状态的LOCK字段,当它取值为Y时,表示该i记录已被某个事务锁定。如果为N,表示该条数据为空闲状态。
在Hibernate中,当通过Session的get()和load()方式来加载一个对象时,可以采用以下方式使用悲观锁:
Customer cus = (Customer)session.get(Customer.class, "c001", LockMode.UPGRADE); //: final org.hibernate.LockMode






LockMode类表示的几种锁定模式

锁定模式 描述
LockMode.NONE 如果在Hibernate缓存中存在Customer对象,就直接返回该引用。否则通过select加载。默认值
LockMode.READ 不管缓存中是否有,都使用select加载数据。如果映射文件设置了版本元素,就执行版本检查,比较缓存中的与取到的是否版本一致。
LockMode.UPGRADE 不管缓存中是否有,都使用select加载数据。就执行版本检查,比较缓存中的与取到的是否版本一致。如果数据库系统支持悲观锁,那么执行select…for update, 否则执行简单的select
LockMode.UPGRADE_NOWAIT 会执行LockMode.UPGRADE和它一样的功能。若果执行的select不能立即获得悲观锁,那么就会抛出异常
LockMode.WRITE 当Hibernate保存或更新对象时,会自动使用这种锁定模式。这种锁定模式只在Hibernate内部使用,所以在应用中不应该使用它


利用Hibernate的版本控制来实现乐观锁
create table Accounts (
id bigint primary key,
name varchar(15),
balance numeric(10,2),
LAST_UPDATED_TIME timestamp,
version int
)

在hbm中配置版本控制
//: 在元素必须跟在<id>元素的后面
<version name = "version" column = "VERSION" />

//: 使用该元素也可以实现版本控制
<timestamp name=”lastUpdateTime” column=” LAST_UPDATED_TIME” />

对游离对象进行版本检查
Session session1 = ....;
trans1 = session1.beginTransaction();
Account a = (Account)session1.get(Account.class, new Long(1));
trans1.commit();
session1.close();

a.setBalance(a.getBalance()-100);

Session session2 = ...;
trans2 = session2.beginTransaction();
session2.lock(a, LockMode.READ);
trans2.commit();
session2.close();

lock() 方法和update()方法之间的区别
lock()方法在LockMode.READ模式下,立即进行版本控制。而update()方法不会立即执行版本检查,只有当Session清理缓存时,真正执行update时才进行版本检查
lock()在默认的LockMode中不会执行update语句。而update()会计划执行一个update语句,如果数据库中没对应的记录那么会抛出异常。

实现乐观锁的其他方式
<class optimistic-lock="all" dynamic-update="true">

//: optimistic-lock=all/dirty 时必须设置dynamic-update为true
posted @ 2009-07-31 09:43 awp110 阅读(139) 评论(0) 编辑

引言

  B/S构架的应用越来越普及,但由于它有别于C/S构架的特殊性,并发控制始终没能得到很好的解决,如售票系统经常会出现同一张火车票出售多次的现象。典型的案例如下:

  例如若有两个客户端,A客户先读取了账户余额2000元,之后B客户也读取了账户余额2000元的数据,A客户提取了500元,对数据库作了变更,此时数据库中的余额为1500元,B客户也要提取1300元,根据其所取得的资料,2000-1300将为700余额,若此时再对数据库进行变更,最后的余额700元就会不正确,应当是200元,问题的出现是由于两个客户对同一条数据进行并发访问造成的。

  Web应用中并发控制的特殊性

  上述问题在C/S构架中可以通过长事务来实现,但Web应用是基于Internet网络环境的,其中的并发控制有其内在的特殊性:

  1. Web所基于的网络协议HTTP(Hyper Text Transfer Protocol)是一种无连接的协议,数据库服务器无法保存事务的状态信息;

  2. 用户可以随时中止或启动浏览器中当前主页上的事务。

  由于上述特殊性,Web应用中并发控制不能采用严格的长事务来实现,但可以长事务的思路来实现,在数据读取的时候把相应的数据锁定,在更新阶段把锁放开,然后更新数据。

  Web应用中并发控制的实现

  业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在 金融 系统的日终结算处理中,我们希望针对某个cut-off时间点的数据进行处理,而不希望在结算进行过程中(可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需要通过一些机制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,就是所谓的“锁”,即给选定的目标数据上锁,使其无法被其他程序修改。有两种锁机制:即通常所说的“乐观锁(Optimistic Locking)” 和“悲观锁(Pessimistic Locking)”。

  1.乐观锁(Optimistic Locking)

  乐观锁(optimistic locking)则乐观的认为资料的存取很少发生同时存取的问题,因而不作数据库层次上的锁定,为了维护正确的数据,乐观锁定使用应用程序上的逻辑实现版本控制来解决。

  并发控制时,数据不一致的情况一旦发生,有几个解决的 方法 ,一种是先更新为主,一种是后更新的为主,比较复杂的就是检查发生变动的数据来实现,或是检查所有属性来实现乐观锁定。

  Hibernate通过版本号检查来实现后更新为主,这也是Hibernate所推荐的方式,在数据库中加入一个VERSON栏记录,在读取数据时连同版本号一同读取,并在更新数据时递增版本号,然后比对版本号与数据库中的版本号,如果大于数据库中的版本号则予以更新,否则就回报错误。

  以Hibernate实现版本号控制锁定的话,我们的对象中增加一个version属性,例如:

public class MyAccount {
private int version;
....
public void setVersion(int version) {
this.version = version;
}
public int getVersion() {
return version;
}
....
}

  而在映像文件中,我们使用optimistic-lock属性设定version控制,属性栏之后增加一个标签,例如:

optimistic-lock="version"

  设定好版本控制之后,在上例中如果B客户试图更新数据,将会引发StableObjectStateException例外,我们可以捕捉这个例外,在处理中重新读取数据库中的数据,同时将B客户目前的数据与数据库中的数据读出来,让B客户有机会比对不一致的数据,以决定要变更的部份,或者您可以设计程式自动读取新的资料,并重复扣款业务流程,直到数据可以更新为止,这一切可以在后台执行,而不用让您的客户知道。在其它架构中也可通过这种思路来实现乐观锁,但版本控制和冲突的检测要在自己程序的程序中实现和维护。

  2.悲观锁(Pessimistic Locking)

  虽然乐观锁能够提高系统的性能,但它是对发生冲突的访问进行事后的补救,应用在用户输入数据量很少的场合比较适合,但如果在 企业 ERP,用户与系统交互涉及大量数据在页面表单上录入,如果事后提交失败后才提示用户要重新录入是很不现实的,所以有必要进行事前控制,这就要采用悲观锁。

  在多个客户端可能读取同一笔数据或同时更新一笔数据的情况下,防止同一个数据被修改而造成混乱,最简单的手段就是在读取时对数据进行锁定,其它客户端不能对同一笔数据进行更  新的读取动作。

  悲观锁定(Pessimistic Locking)一如其名称所示,悲观的认定每次资料存取时,其它的客户端也会存取同一笔数据,因此对该笔数据进行事先锁定,直到自己操作完成后解除锁定。

  悲观锁定通常透过系统或数据库本身的功能来实现,依赖系统或数据库本身提供的锁定机制,Hibernate即是如此,我们可以利用Query或Criteria的setLockMode()方法来设定要锁定的表或列(row)及其锁定模式,锁定模式有以下的几个:

      LockMode.WRITE:在insert或update时进行锁定,Hibernate会在save()方法时自动获得锁定。

  LockMode.UPGRADE:利用SELECT … FOR UPDATE进行锁定。

  LockMode.UPGRADE_NOWAIT:利用SELECT … FOR UPDATE NOWAIT进行锁定,在Oracle环境下使用。

  LockMode.READ:在读取记录时Hibernate会自动获得锁定。

  LockMode.NONE:没有锁定。

  也可以在使用Session的load()或是lock()时指定锁定模式以进行锁定。

  如果数据库不支持所指定的锁定模式,Hibernate会选择一个合适的锁定替换,而不是丢出一个例外。

  3.其它构架中悲观锁的实现

  Hibernate的悲观锁,也是基于数据库的锁机制实现。下面的代码实现了对“用户”查询记录的加锁:

  String sqlStr = "from userInfo as user where user.userId=’admin’";Query query = session.createQuery(sqlStr);query.setLockMode("user",LockMode.UPGRADE); //加锁List userList = query.list();//执行查询,获取数据

  query.setLockMode对查询语句中,特定别名所对应的记录进行加锁(我们为userInfo类指定了一个别名“user”),这里也就是对返回的所有user记录进行加锁:

select tuser0_.id as id, tuser0_.userId as userId, tuser0_.group_id as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex from t_user tuser0_ where (tuser0_.userId =’admin’ ) for update

  通过上述转换后的sql语句可知,Hibernate的加锁其实是利用了数据库的for update语句,在读取阶段对某条记录的锁定,而在更新阶段提交,释放锁。

  其实其它架构也可以采取该思路,不过,数据库的for update语句的锁定和释放一定要在数据的同一个连接中,如果读取阶段和更新阶段不是统一连接,即读取之后断开了与数据库的连接,则for update语句的锁定立即失效,为此,如果其它架构中要采取这种方式则要做相应的调整。

  首先,由于Web应用是无状态的,也就是说数据库的for update语句的锁定和释放不一定是数据的同一个连接,为此,采用痕迹跟踪法,在读取数据时生成唯一的序列号(serialId),建立与数据连接的映射,并放置一个map数据结构中;在更新时,通过该serialId在连接池中重新获取该连接,用该连接去更新数据。

  如果系统是采用dao读取数据,实体bean去更新数据,则只要在更新数据之前断开读取数据时的连接,则可以通过其它途径更新数据,如下代码所示:

public void update (AbstractEntityData data, String[] selTeamName ,String serialId) throws Exception {dao.closeConnect(serialId);bo.update(data);}

  其中,dao.closeConnect(serialId)是断开数据连接,bo.update(data)是通过EJB更新数据库

  4.序列号(serialId)的创建和维护

  由于不同用户可能同时建立连接或同一用户先后建立连接,故创建序列号可以在读取数据时通过sessionId和时间戳组合而成。而在操作的过程中,为了保持序列号不会丢失和唯一性,它不能放在session或application中,而是放在页面的request对象里,通过它向其它页面传递。

  5.关联表的锁定

  其实,Hibernate的悲观锁方式只能对单个表的记录进行锁定,但现实中,存在关联更新的情况,即在更新主表的时候有可能会更新到与之相关的子表,与此同时,其它用户也可能通过其它主表更新相应的子表同一条记录。

  有两种方式处理,一是在读取数据通过sql语句关联子表相应记录,因为for update对所有关联表中符合条件的记录都会加锁;二是为子表找一个入口表,在更新子表的同时,必须更新子表的入口表。

  6.例外操作的处理

  采用这种方式,有一些例外情况必须小心处理,一是页面的关闭,如果调用相应的方法,如onbeforeunload()等,释放对应的数据库连接;二是用户非正常关机退出系统,必须有数据库周期清除无用的连接,如间隔二十分钟等,来释放读取时对数据的锁定,否则,该数据会长时间被锁定,直至应用服务器重启。

  结论

  软件系统的并发控制一般是通过加锁来实现,同样,Web应用也是采用乐观锁和悲观锁来实现,乐观锁是一种事后补救措施,是通过程序的逻辑控制版本来实现的,而悲观锁是事前的一种预防措施,它利用数据库的锁机制来实现,Hibernate对它做了一层封装,使应用更加方便,为了让其它架构都能适用,本文还原了Hibernate的实现原理,提出一般的实现思路和注意实现。

posted @ 2009-07-31 09:27 awp110 阅读(115) 评论(0) 编辑