C#语言有很多值得学习的地方,这里我们主要介绍C# lock关键字,包括介绍Monitor 类锁定一个对象等方面。
C#提供了一个lock关键字,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,C# lock关键字定义如下:
lock(expression) statement_block
expression代表你希望跟踪的对象,通常是对象引用。
◆如果你想保护一个类的实例,一般地,你可以使用this;
◆如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。
而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。
下面是一个使用C# lock关键字的典型例子,在注释里说明了C# lock关键字的用法和用途。
示例如下:
- using System;
- using System.Threading;
- namespace ThreadSimple
- {
- internal class Account
- {
- int balance;
- Random r = new Random();
- internal Account(int initial)
- {
- balance = initial;
- }
- internal int Withdraw(int amount)
- {
- if (balance < 0)
- {
- //如果balance小于0则抛出异常
- throw new Exception("Negative Balance");
- }
- //下面的代码保证在当前线程修改balance的值完成之前
- //不会有其他线程也执行这段代码来修改balance的值
- //因此,balance的值是不可能小于0 的
- lock (this)
- {
- Console.WriteLine("Current Thread:"+Thread.CurrentThread.Name);
- //如果没有lock关键字的保护,那么可能在执行完if的条件判断之后
- //另外一个线程却执行了balancebalance=balance-amount修改了balance的值
- //而这个修改对这个线程是不可见的,所以可能导致这时if的条件已经不成立了
- //但是,这个线程却继续执行balancebalance=balance-amount,所以导致balance可能小于0
- if (balance >= amount)
- {
- Thread.Sleep(5);
- balancebalance = balance - amount;
- return amount;
- }
- else
- {
- return 0; // transaction rejected
- }
- }
- }
- internal void DoTransactions()
- {
- for (int i = 0; i < 100; i++)
- Withdraw(r.Next(-50, 100));
- }
- }
- internal class Test
- {
- static internal Thread[] threads = new Thread[10];
- public static void Main()
- {
- Account acc = new Account (0);
- 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].Name=i.ToString();
- for (int i = 0; i < 10; i++)
- threads[i].Start();
- Console.ReadLine();
- }
- }
- }
Monitor 类锁定一个对象
当多线程公用一个对象时,也会出现和公用代码类似的问题,这种问题就不应该使用C# lock关键字了,这里需要用到System.Threading中的一个类Monitor,我们可以称之为监视器,Monitor提供了使线程共享资源的方案。
Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。对象锁机制保证了在可能引起混乱的情况下一个时刻只有一个线程可以访问这个对象。 Monitor必须和一个具体的对象相关联,但是由于它是一个静态的类,所以不能使用它来定义对象,而且它的所有方法都是静态的,不能使用对象来引用。下面代码说明了使用Monitor锁定一个对象的情形:
- ......
- Queue oQueue=new Queue();
- ......
- Monitor.Enter(oQueue);
- ......//现在oQueue对象只能被当前线程操纵了
- Monitor.Exit(oQueue);//释放锁
如上所示,当一个线程调用Monitor.Enter()方法锁定一个对象时,这个对象就归它所有了,其它线程想要访问这个对象,只有等待它使用 Monitor.Exit()方法释放锁。为了保证线程最终都能释放锁,你可以把Monitor.Exit()方法写在try-catch- finally结构中的finally代码块里。
一、C#语句概述(这部分内容来自网络)
语句(statement):程序的活动是通过语句来表达的。C#支持多种不同的语句,许多语句是以嵌入语句的形式定义的。
块(block):允许在只能使用单个语句的上下文中编写多个语句。块由一个括在大括号“{}”内的语句列表组成。
声明语句(declaration statement):用于声明局部变量和常量。
表达式语句(expression statement):表达式包括方法调用、使用new运算符进行对象分配、使用“=”和复合赋值运算符进行赋值,以及使用“++”和“--”运算符进行增量和减量的运算等。
选择语句(selection statement):用于根据某个表达式的值,选择执行若干可能语句中的某一个。
If else语句
switch语句
迭代(循环)语句(iteration statement):用于重复执行嵌入语句
While语句
Do While语句
For语句
foreach语句。
跳转语句(jump statement):用于传递程序控制。
Break语句
Continue语句
Goto语句
Throw语句
return语句
异常处理语句(try-catch-finally):用于捕捉在块的执行期间发生的异常。
Try-catch语句
Try-finally语句
Try-catch-finally语句
try-[catch]-finally语句用于指定一个终止代码块,不管异常出现与否,它总是被执行。
二、checked和unchecked语句:用于控制整型算术运算和转换的溢出检查。
checked检查它作用的域中可能出现的违例,并抛出一个异常;而unchecked则阻止所有的检查。
如:
三、lock语句:用于获取给定对象的互斥锁,执行语句,然后释放该锁。
lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入一个锁定代码,则它将在释放该对象前一直等待,此语句的形式如下:
lock(expression) statement_block
其中:
expression
指定要锁定的对象。expression 必须是引用类型。
通常,如果要保护实例变量,则 expression 为 this;如果要保护 static 变量(或者如果临界区出现在给定类的静态方法中),则 expression 为 typeOf (class)。
statement_block
临界区的语句。
下列形式的 lock 语句
lock (x) ...
(其中 x 是一个引用类型的表达式)完全等效于
System.Threading.Monitor.Enter(x);
try
{
}
finally
{
System.Threading.Monitor.Exit(x);
}
不同的只是:实际执行中 x 只计算一次。
当一个互斥锁已被占用时,在同一线程中执行的代码仍可以获取和释放该锁。但是,在其他线程中执行的代码在该锁被释放前是无法获得它的。
class Account
{
decimal Totail;
public void Payment(decimal Consume)
{
lock(this)
{
if (Consume > Totail)
{
throw new Exception("帐户余额不足,不能支付!");
}
else
{
Totail -= Consume;
}
}
}
}
四、using语句:
1、 命名空间指示符,用于引用命名空间
如:using Sysytem;
2、 别名指示符(指定命名空间或类的别名)
如:using Dos=System.Console;
3、 资源管理的语句功能
如:using(Font F= new Font(“宋体”,12))
{
Console.WriteLine(F.Italic.ToString());
}//运行结束时,释放了F对象
示例 :
//#define Debug
#undef Debug
//using 用作命名空间指示符
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace statement
{
//指定Font类的别名为F
using F = System.Drawing.Font;
class Program
{
static void Main(string[] args)
{
//using 语句管理资源的用法
using (TextWriter W = File.CreateText("E://test.txt"))
{
W.WriteLine(@"using 语句使用:using 语句允许程序员指定使用资源的对象应当何时释放资源。有资源管理的语句功能");
//使用别名来实例化对象
F font = new F("宋体",12);
W.WriteLine(font.Name.ToString() + font.Size.ToString());
}
//上面的using语句等价于下面的预编译语句
#if Debug
TextWriter w = File.CreateText("E://test.txt");
try
{
w.WriteLine(@"using 语句使用:using 语句允许程序员指定使用资源的对象应当何时释放资源。有资源管理的语句功能");
}
finally
{
//标准写法,下面语句也可以直接写成w.Dispose()
if(w != null)((IDisposable)w).Dispose();
}
#endif
//可以在using 语句中声明对象也可以在using 语句之前声明对象,如下:
TextReader R = File.OpenText("E://test.txt");
using (R)
{
string Stringd = R.ReadToEnd();
Console.WriteLine(Stringd);
}
}
}
}
int i = int.MaxValue;
checked
{
Console.WriteLine(i + 1); //报一个为处理异常
}
unchecked
{
Console.WriteLine(i + 1); //不进行益处检查,返回一个错误的结果
}
看了如果理解还不是很深入,再看看下面的代码
class checkedTest
{
const int x = 1000000;
const int y = 1000000;
static int F()
{
checked { return (x * y); } //编译器警告:运算在编译时溢出
}
static int G()
{
unchecked { return (x * y); } // 不进行溢出检查,返回-727379968
}
static int H()
{
return x * y; // 编译器警告:运算在编译时溢出
}
static int M()
{
//checked和unchecked不能对函数的返回值进行操作
checked { return (int)(Decimal.Multiply(x, y)); } //与return (int)(Decimal.Multiply(x, y));相同,在编译的时候不会检查
}
static void Main()
{
Console.WriteLine(F());
Console.WriteLine(G());
Console.WriteLine(H());
Console.WriteLine(M());
}
}