蛙蛙推荐:改进了一个DBAccess类,顺便说说啥是线程安全
改进了一个DBAccess类,顺便说说啥是线程安全
原文:
我的DbHelper数据操作类
http://www.cnblogs.com/fanrong/archive/2007/04/25/726526.html
源码下载地址
https://files.cnblogs.com/onlytiancai/WawaDbAccess.rar
修改如下
加了一个参数帮助类,如下,从旧版petshop拆出来的,功能是缓存每个命令的参数。
经测试,代码一行也不用改,只改配置文件,在access和sqlserver2005下都能通过。
DbHelper类里加了两个方法,如下,功能分别是把参数缓存起来和从缓存里初始化参数
使用如下
sqlserver的配置
access的配置
sqlserver的建表脚本
access的表结构
原文的链接里有个朋友说DbHelper类每个方法都没有考虑线程安全性,我想了半天也没看出哪儿有问题来,不知道如何改进那个类在多线程下的表现。如frankroast所说:
【我们说一个方法是否是线程安全的,判断的标准只有一个,这个方法是否访问了 可以写的“成员变量”。(所谓的state)
注意,只有两个条件:(1)可以改变的 (2)成员变量
如果访问了可以写的“成员变量”,那么这个方法不是 线程安全的。
如果没有访问 可以写的“成员变量”,那么这个方法是 线程安全的。】
dbhelper的类只有3个变量,dbProviderName,dbConnectionString和connection都可以声明为readonly,它本身的方法,无论静态的还是非静态的都没有访问可写的静态变量和全局变量,我觉得多线程下也应该是安全的。
另如lithium所说:
【1.静态方法如果会更改静态的成员变量,为了线程安全,同样需要同步,只不过这时获得的不是对象实例的锁,而是该类的class对象的锁
2.如果实例方法不会更改对象的状态,静态方法不更改静态的成员变量,那么方法就是可重入方法,也就是线程安全的。】
dbhelper也没有违反这两条,还是不需要加锁。
david又说了一下原理
【关于线程安全:
如果某函数是不可重入的,则它不是线程安全的!
至于是否可重入,就可以根据这个函数是否使用了静态存储的数据判断了!
从语意上说,静态存储数据包括全局变量和局部静态变量,它们都是存储在堆上的.而线程间是共享同一进程空间的,所以它们共享相同的静态存储数据, 所以...
对于可重入函数来说,如果下一次函数调用对上一次调用产生影响的话,那么该函数就是不可重入函数了.
】
他这里说的下一次函数调用对上一次调用产生影响是什么意思呀,也没举个例子。我理解的是一个方法第一次调用还没有结束的时候,另一个线程也调用了这个方法,并且改变了第一次调用所访问的类成员(无论静态成员还是实例成员),如果第一次调用里已经认为全局成员A的值为1,并进入一个if语句,而另一个线程里却把全局成员A的值改成0了,这样第一次调用里的逻辑就不对了,或者第一次的if语句里把全局成员A的值改成3了,那另一个线程里把A的值改成1的操作就丢失了。这里没写代码来做示例,也没画图说明一下,但大家应该能理解,有点像sqlserver的事务。
最后我得出的结论如下,不知道对不对:
1、把一个类里的所有非readonly的成员,都为其声明一个object的锁对象,锁对象的可访问性和非readonly成员的可访问性一样,成员是public,锁也是public,成员是private,锁也是private,成员是static,锁也是static。然后凡是有需要访问该成员的方法,无论是读取还是修改,无论是本类自己的方法,还是外面的类的方法,都在访问成员的代码外层用lock括起来,lock的对象就是为该成员单独创建的那个object的锁。这样多线程就不会出问题了。
msdn里明确说过,不要lock(this),lock(值类型),lock(字符串),lock(typeof(this))等,所以最好就是为每个非readonly的成员分配一个锁。大家对此解决方案有啥不同意见,给指正一下,比如说锁太大,有些没必要等,说一下理由。
忘了贴参考链接了,不好意思
在多线程中使用静态方法是否有线程安全问题
http://blog.csdn.net/scucj/archive/2006/11/18/1394523.aspx
这种静态方法有线程安全问题吗?
http://www.javaeye.com/t/14315.html
方法的线程安全(静态方法调用问题2)
http://blog.csdn.net/frankroast/archive/2005/03/25/330379.aspx
锁住局部静态变量确保线程安全
http://blog.joycode.com/ninputer/archive/2004/08/26/31633.aspx
.NET中静态变量的使用需要注意线程安全问题
http://www.cnblogs.com/hhh/archive/2007/01/31/636299.html
线程,同步与锁————Lock你到底锁住了谁
http://www.cnblogs.com/city22/archive/2007/01/30/634948.html
再论线程安全,关键字(C#,.Net,Cache,线程安全)
http://www.lukiya.com/Blogs/2007/07/27/Post-561.html
HashTable的线程安全性
http://www.cnblogs.com/idior/archive/2005/04/01/130095.html
原文:
我的DbHelper数据操作类
http://www.cnblogs.com/fanrong/archive/2007/04/25/726526.html
源码下载地址
https://files.cnblogs.com/onlytiancai/WawaDbAccess.rar
修改如下
加了一个参数帮助类,如下,从旧版petshop拆出来的,功能是缓存每个命令的参数。
经测试,代码一行也不用改,只改配置文件,在access和sqlserver2005下都能通过。
public class DbParametersHelper
{
private static Hashtable parmCache = Hashtable.Synchronized(new Hashtable());
public static void CacheParameters(string cacheKey, params DbParameter[] cmdParms)
{
parmCache[cacheKey] = cmdParms;
}
public static DbParameter[] GetCachedParameters(string cacheKey)
{
DbParameter[] cachedParms = (DbParameter[])parmCache[cacheKey];
if (cachedParms == null)
return null;
DbParameter[] clonedParms = new DbParameter[cachedParms.Length];
for (int i = 0, j = cachedParms.Length; i < j; i++)
clonedParms[i] = (DbParameter)((ICloneable)cachedParms[i]).Clone();
return clonedParms;
}
}
{
private static Hashtable parmCache = Hashtable.Synchronized(new Hashtable());
public static void CacheParameters(string cacheKey, params DbParameter[] cmdParms)
{
parmCache[cacheKey] = cmdParms;
}
public static DbParameter[] GetCachedParameters(string cacheKey)
{
DbParameter[] cachedParms = (DbParameter[])parmCache[cacheKey];
if (cachedParms == null)
return null;
DbParameter[] clonedParms = new DbParameter[cachedParms.Length];
for (int i = 0, j = cachedParms.Length; i < j; i++)
clonedParms[i] = (DbParameter)((ICloneable)cachedParms[i]).Clone();
return clonedParms;
}
}
DbHelper类里加了两个方法,如下,功能分别是把参数缓存起来和从缓存里初始化参数
public void CachedParameters(DbCommand cmd)
{
DbParameterCollection paramColl = cmd.Parameters;
DbParameter[] parms = new DbParameter[paramColl.Count];
for (int i = 0; i < paramColl.Count; i++)
{
parms[i] = paramColl[i];
}
DbParametersHelper.CacheParameters(
string.Format("{0}{1}", cmd.Connection.ConnectionString, cmd.CommandText),
parms);
}
public bool initParametersFromCache(DbCommand cmd)
{
DbParameter[] parms = DbParametersHelper.GetCachedParameters(
string.Format("{0}{1}", cmd.Connection.ConnectionString, cmd.CommandText));
if (parms == null)
return false;
for (int i = 0; i < parms.Length; i++)
{
cmd.Parameters.Add(parms[i]);
}
return true;
}
{
DbParameterCollection paramColl = cmd.Parameters;
DbParameter[] parms = new DbParameter[paramColl.Count];
for (int i = 0; i < paramColl.Count; i++)
{
parms[i] = paramColl[i];
}
DbParametersHelper.CacheParameters(
string.Format("{0}{1}", cmd.Connection.ConnectionString, cmd.CommandText),
parms);
}
public bool initParametersFromCache(DbCommand cmd)
{
DbParameter[] parms = DbParametersHelper.GetCachedParameters(
string.Format("{0}{1}", cmd.Connection.ConnectionString, cmd.CommandText));
if (parms == null)
return false;
for (int i = 0; i < parms.Length; i++)
{
cmd.Parameters.Add(parms[i]);
}
return true;
}
使用如下
class Program
{
const string ADD_USER_SQL = "insert into U_Users([userid],[username],[nickname],[password],[hashpassword],[createdate],[createdip],[status])" +
"values(@userid,@username,@nickname,@password,@hashpassword " +
",@createdate,@createip,0)";
public static void AddUser(string username, string password, string nickname, string createip)
{
DbHelper db = new DbHelper();
DbCommand cmd = db.GetSqlStringCommond(ADD_USER_SQL);
if (db.initParametersFromCache(cmd))
{
cmd.Parameters[0].Value = 1;
cmd.Parameters[1].Value = username;
cmd.Parameters[2].Value = nickname;
cmd.Parameters[3].Value = password;
cmd.Parameters[4].Value = password;
cmd.Parameters[5].Value = DateTime.UtcNow;
cmd.Parameters[6].Value = createip;
}
else
{
db.AddInParameter(cmd, "@userid", DbType.Int32, 1);
db.AddInParameter(cmd, "@username", DbType.String, username);
db.AddInParameter(cmd, "@nickname", DbType.String, nickname);
db.AddInParameter(cmd, "@password", DbType.String, password);
db.AddInParameter(cmd, "@hashpassword", DbType.String, password);
db.AddInParameter(cmd, "@createdate", DbType.Date, DateTime.UtcNow);
db.AddInParameter(cmd, "@createip", DbType.String, createip);
db.CachedParameters(cmd);
}
db.ExecuteNonQuery(cmd);
}
static void Main(string[] args)
{
try
{
AddUser("onlytiancai", "##$%&SDF", "蛙蛙王子", "192.168.0.1");
AddUser("tiancai", "3234234", "水然枫林醉", "192.168.0.23");
Console.WriteLine("ok");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
Console.Read();
}
}
{
const string ADD_USER_SQL = "insert into U_Users([userid],[username],[nickname],[password],[hashpassword],[createdate],[createdip],[status])" +
"values(@userid,@username,@nickname,@password,@hashpassword " +
",@createdate,@createip,0)";
public static void AddUser(string username, string password, string nickname, string createip)
{
DbHelper db = new DbHelper();
DbCommand cmd = db.GetSqlStringCommond(ADD_USER_SQL);
if (db.initParametersFromCache(cmd))
{
cmd.Parameters[0].Value = 1;
cmd.Parameters[1].Value = username;
cmd.Parameters[2].Value = nickname;
cmd.Parameters[3].Value = password;
cmd.Parameters[4].Value = password;
cmd.Parameters[5].Value = DateTime.UtcNow;
cmd.Parameters[6].Value = createip;
}
else
{
db.AddInParameter(cmd, "@userid", DbType.Int32, 1);
db.AddInParameter(cmd, "@username", DbType.String, username);
db.AddInParameter(cmd, "@nickname", DbType.String, nickname);
db.AddInParameter(cmd, "@password", DbType.String, password);
db.AddInParameter(cmd, "@hashpassword", DbType.String, password);
db.AddInParameter(cmd, "@createdate", DbType.Date, DateTime.UtcNow);
db.AddInParameter(cmd, "@createip", DbType.String, createip);
db.CachedParameters(cmd);
}
db.ExecuteNonQuery(cmd);
}
static void Main(string[] args)
{
try
{
AddUser("onlytiancai", "##$%&SDF", "蛙蛙王子", "192.168.0.1");
AddUser("tiancai", "3234234", "水然枫林醉", "192.168.0.23");
Console.WriteLine("ok");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
Console.Read();
}
}
sqlserver的配置
<appSettings>
<add key="DbHelperProvider" value="System.Data.SqlClient"/>
<add key="DbHelperConnectionString" value="Data Source=."SQLEXPRESS;AttachDbFilename=H:"KM_SVN"sync"src"db"KM.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True"/>
</appSettings>
<add key="DbHelperProvider" value="System.Data.SqlClient"/>
<add key="DbHelperConnectionString" value="Data Source=."SQLEXPRESS;AttachDbFilename=H:"KM_SVN"sync"src"db"KM.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True"/>
</appSettings>
access的配置
<appSettings>
<add key="DbHelperProvider" value="System.Data.OleDb"/>
<add key="DbHelperConnectionString" value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=H:"KM_SVN"sync"src"db"km.mdb;Persist Security Info=False"/>
</appSettings>
<add key="DbHelperProvider" value="System.Data.OleDb"/>
<add key="DbHelperConnectionString" value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=H:"KM_SVN"sync"src"db"km.mdb;Persist Security Info=False"/>
</appSettings>
sqlserver的建表脚本
CREATE TABLE [dbo].[U_Users](
[UserID] [int] NOT [UserID] [int] NOT NULL,
NULL,
[Username] [varchar](16) COLLATE Chinese_PRC_CI_AS NOT NULL,
[Nickname] [nvarchar](16) COLLATE Chinese_PRC_CI_AS NOT NULL,
[Password] [varchar](16) COLLATE Chinese_PRC_CI_AS NOT NULL,
[HashPassword] [varchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL,
[CreateDate] [datetime] NOT NULL CONSTRAINT [DF_U_Users_CreateDate] DEFAULT (getdate()),
[CreatedIP] [varchar](32) COLLATE Chinese_PRC_CI_AS NOT NULL,
[LastLoginIp] [varchar](32) COLLATE Chinese_PRC_CI_AS NULL,
[LastLoginTime] [datetime] NULL CONSTRAINT [DF_U_Users_LastLoginTime] DEFAULT (getdate()),
[Status] [tinyint] NOT NULL CONSTRAINT [DF_U_Users_Status] DEFAULT ((0))
) ON [PRIMARY]
[UserID] [int] NOT [UserID] [int] NOT NULL,
NULL,
[Username] [varchar](16) COLLATE Chinese_PRC_CI_AS NOT NULL,
[Nickname] [nvarchar](16) COLLATE Chinese_PRC_CI_AS NOT NULL,
[Password] [varchar](16) COLLATE Chinese_PRC_CI_AS NOT NULL,
[HashPassword] [varchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL,
[CreateDate] [datetime] NOT NULL CONSTRAINT [DF_U_Users_CreateDate] DEFAULT (getdate()),
[CreatedIP] [varchar](32) COLLATE Chinese_PRC_CI_AS NOT NULL,
[LastLoginIp] [varchar](32) COLLATE Chinese_PRC_CI_AS NULL,
[LastLoginTime] [datetime] NULL CONSTRAINT [DF_U_Users_LastLoginTime] DEFAULT (getdate()),
[Status] [tinyint] NOT NULL CONSTRAINT [DF_U_Users_Status] DEFAULT ((0))
) ON [PRIMARY]
access的表结构
[Username] 数字
[Nickname] 文本
[Password] 文本
[HashPassword] 文本
[CreateDate] 日期/时间
[CreatedIP] 文本
[LastLoginIp] 文本
[LastLoginTime] 日期/时间
[Status] 数字
[Nickname] 文本
[Password] 文本
[HashPassword] 文本
[CreateDate] 日期/时间
[CreatedIP] 文本
[LastLoginIp] 文本
[LastLoginTime] 日期/时间
[Status] 数字
原文的链接里有个朋友说DbHelper类每个方法都没有考虑线程安全性,我想了半天也没看出哪儿有问题来,不知道如何改进那个类在多线程下的表现。如frankroast所说:
【我们说一个方法是否是线程安全的,判断的标准只有一个,这个方法是否访问了 可以写的“成员变量”。(所谓的state)
注意,只有两个条件:(1)可以改变的 (2)成员变量
如果访问了可以写的“成员变量”,那么这个方法不是 线程安全的。
如果没有访问 可以写的“成员变量”,那么这个方法是 线程安全的。】
dbhelper的类只有3个变量,dbProviderName,dbConnectionString和connection都可以声明为readonly,它本身的方法,无论静态的还是非静态的都没有访问可写的静态变量和全局变量,我觉得多线程下也应该是安全的。
另如lithium所说:
【1.静态方法如果会更改静态的成员变量,为了线程安全,同样需要同步,只不过这时获得的不是对象实例的锁,而是该类的class对象的锁
2.如果实例方法不会更改对象的状态,静态方法不更改静态的成员变量,那么方法就是可重入方法,也就是线程安全的。】
dbhelper也没有违反这两条,还是不需要加锁。
david又说了一下原理
【关于线程安全:
如果某函数是不可重入的,则它不是线程安全的!
至于是否可重入,就可以根据这个函数是否使用了静态存储的数据判断了!
从语意上说,静态存储数据包括全局变量和局部静态变量,它们都是存储在堆上的.而线程间是共享同一进程空间的,所以它们共享相同的静态存储数据, 所以...
对于可重入函数来说,如果下一次函数调用对上一次调用产生影响的话,那么该函数就是不可重入函数了.
】
他这里说的下一次函数调用对上一次调用产生影响是什么意思呀,也没举个例子。我理解的是一个方法第一次调用还没有结束的时候,另一个线程也调用了这个方法,并且改变了第一次调用所访问的类成员(无论静态成员还是实例成员),如果第一次调用里已经认为全局成员A的值为1,并进入一个if语句,而另一个线程里却把全局成员A的值改成0了,这样第一次调用里的逻辑就不对了,或者第一次的if语句里把全局成员A的值改成3了,那另一个线程里把A的值改成1的操作就丢失了。这里没写代码来做示例,也没画图说明一下,但大家应该能理解,有点像sqlserver的事务。
最后我得出的结论如下,不知道对不对:
1、把一个类里的所有非readonly的成员,都为其声明一个object的锁对象,锁对象的可访问性和非readonly成员的可访问性一样,成员是public,锁也是public,成员是private,锁也是private,成员是static,锁也是static。然后凡是有需要访问该成员的方法,无论是读取还是修改,无论是本类自己的方法,还是外面的类的方法,都在访问成员的代码外层用lock括起来,lock的对象就是为该成员单独创建的那个object的锁。这样多线程就不会出问题了。
msdn里明确说过,不要lock(this),lock(值类型),lock(字符串),lock(typeof(this))等,所以最好就是为每个非readonly的成员分配一个锁。大家对此解决方案有啥不同意见,给指正一下,比如说锁太大,有些没必要等,说一下理由。
忘了贴参考链接了,不好意思
在多线程中使用静态方法是否有线程安全问题
http://blog.csdn.net/scucj/archive/2006/11/18/1394523.aspx
这种静态方法有线程安全问题吗?
http://www.javaeye.com/t/14315.html
方法的线程安全(静态方法调用问题2)
http://blog.csdn.net/frankroast/archive/2005/03/25/330379.aspx
锁住局部静态变量确保线程安全
http://blog.joycode.com/ninputer/archive/2004/08/26/31633.aspx
.NET中静态变量的使用需要注意线程安全问题
http://www.cnblogs.com/hhh/archive/2007/01/31/636299.html
线程,同步与锁————Lock你到底锁住了谁
http://www.cnblogs.com/city22/archive/2007/01/30/634948.html
再论线程安全,关键字(C#,.Net,Cache,线程安全)
http://www.lukiya.com/Blogs/2007/07/27/Post-561.html
HashTable的线程安全性
http://www.cnblogs.com/idior/archive/2005/04/01/130095.html