C# 多线程——中
4、多线程实现
(1)ThreadStart委托和ParameterizedThreadStart委托
在调用Thread.Start()方法后,系统以异步的方式运行threadstart或ParameterizedThreadStart委托绑定的方法。ParameterizedThreadStart委托是面向带参数方法的,对应方法的参数为object,此参数可以是一个值对象或自定义对象。
class Program
{
static Thread thread_1;
static Thread thread_2;
static void Main(string[] args)
{
Console.WriteLine("Start Run...");
//使用threadstart启动线程(无参数)
ThreadStart ts_1 = new ThreadStart(count_1);
thread_1 = new Thread(ts_1);
thread_1.Start();
thread_1.Join();//阻塞线程,调用线程结束后执行其他线程
Console.WriteLine("other thread");
//使用ParameterizedThreadStart启动线程(有参数)
ParameterizedThreadStart pts_2 = new ParameterizedThreadStart(count_1);
thread_2 = new Thread(pts_2);
thread_2.Start(100);
thread_2.Join();
Console.WriteLine("Run Over...");
}
static void count_1()
{
for(int i=0;i<5;i++)
{
Console.WriteLine("the number is:{0}",i);
}
}
static void count_1(object count)
{
for (int i = (int)count; i < (int)count + 5; i++)
{
Console.WriteLine("the number is:{0}", i);
}
}
}
运行结果:
删除代码中thread_1.Join();和thread_2Join();两行程序后,运行结果如下:
可见join的作用,当前线程委托的方法立刻执行,阻塞其他线程,线程调用结束后,在执行其他线程。
在系统无法预知异步线程需要的运行时间时,使用sleep方法并不能准确阻塞线程,这时需要加入join方法,能保证主线程序在异步线程结束后才会停止,如下代码:(上述代码进行部分修改)
Console.WriteLine("Start Run...");
//使用threadstart启动线程(无参数)
ThreadStart ts_1 = new ThreadStart(count_1);
thread_1 = new Thread(ts_1);
thread_1.Start();
thread_1.Join();//阻塞线程,调用线程结束后执行其他线程
Console.WriteLine("other thread");
//使用ParameterizedThreadStart启动线程(有参数)
ParameterizedThreadStart pts_2 = new ParameterizedThreadStart(count_1);
thread_2 = new Thread(pts_2);
thread_2.Start(100);
Console.WriteLine("Do Something...");
Console.WriteLine("The Second Thread is waiting");
Console.WriteLine("Run Second Thread");
thread_2.Join();
Console.WriteLine("Run Over...");
运行结果:
(2)前台线程与后台线程
thread属性IsBackground,通过把该属性设置为true,就可以把线程设置为后台线程。Thread.Start()启动的线程默认为前台线程,在进程中,只要有一个前台线程未退出,进程就不会终止。主线程就是一个前台线程。
而后台线程不管线程是否结束,只要所有的前台线程都退出(包括正常退出和异常退出)后,进程就会自动终止。一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序。
(3)挂起、恢复、终止线程
suspend(挂起)和resume(恢复)不推荐使用。使用suspend使线程长期处于挂起状态,当在其他线程调用这些资源的时候会引起死锁,所以在没必要情况下不推荐使用这两个方法。
若想终止正在运行的线程,可以使用Abort()方法。在使用Abort()的时候,将引发一个特殊异常 ThreadAbortException 。若想在线程终止前恢复线程的执行,可以在捕获异常后 ,在catch(ThreadAbortException ex){…} 中调用Thread.ResetAbort()取消终止。而使用Thread.Join()可以保证应用程序域等待异步线程结束后才终止运行。
stop()方法,可以突然停止线程,它可能导致数据对象的崩溃,在JDK1.2后便被淘汰。线程在调用stop()方法时,线程会释放它当前持有的所有锁,导致对象中的数据处于不一致状态崩溃,且无任何警告。
(4)线程同步
当多个线程共享资源时可能造成冲突,需引入线程同步机制。线程同步的意思是:几个线程之间要排队,一个一个对共享资源进行操作,而不是同步操作。
static void main(string[] args)
{
Thread threadA = new Thread(Method);
threadA.Name = "Tom";
Thread threadB = new Thread(Method);
threadB.Name = "Jerry";
threadA.Start();
threadB.Start();
Console.ReadKey();
}
static void Method(object sender)
{
for(int i=0;i<5;i++)
{
Console.WriteLine("线程名:{0},循环{1}次", Thread.CurrentThread.Name, i);
Thread.Sleep(200);
}
}
运行结果:
方法一:使用join方法
join()的方法以在前面介绍过,现在直接展示代码
static void main(string[] args)
{
Thread threadA = new Thread(Method);
threadA.Name = "Tom";
Thread threadB = new Thread(Method);
threadB.Name = "Jerry";
threadA.Start();
threadA.Join();
threadB.Start();
Console.ReadKey();
}
static void Method(object sender)
{
for(int i=0;i<5;i++)
{
Console.WriteLine("线程名:{0},循环{1}次", Thread.CurrentThread.Name, i);
Thread.Sleep(200);
}
}
运行结果:
方法二:Lock关键字
lock(this) 锁定 当前实例对象,如果有多个类实例的话,lock锁定的只是当前类实例,对其它类实例无影响。所有不推荐使用。
lock(typeof(type))锁定的是type类的所有实例。
lock(obj)锁定的对象是全局的私有化静态变量。外部无法对该变量进行访问。
static object obj = new object();
static void Main(string[] args)
{
Thread threadA = new Thread(Method);
threadA.Name = "Tom";
Thread threadB = new Thread(Method);
threadB.Name = "Jerry";
threadA.Start();
threadB.Start();
Console.ReadKey();
}
static void Method(object sender)
{
lock(obj)
{
for(int i=0;i<5;i++)
{
Console.WriteLine("线程名:{0},循环{1}次", Thread.CurrentThread.Name, i);
Thread.Sleep(200);
}
}
}
运行结果:
方法三:使用Monitor类实现线程同步
上述代码进行替换
lock(obj)
{
//代码段
}
替换为
Monitor.Enter(obj);
//代码段
Monitor.Exit(obj);
运行结果与上面一致。
Monitor.TryEnter(object)和Enter(object)方法类似,但是不会像Enter强制执行,如果线程成功进入代码区域返回true。
可以通过TryEnter(object,1000)设置1S的超时时间,如果在1S内没有获得同步锁,则返回false。
Wait(object)方法:释放对象上的锁并阻止当前线程,直到它重新获取该锁,该线程进入等待队列。
Pulse方法:只有锁的当前所有者可以使用 Pulse 向等待对象发出信号,当前拥有指定对象上的锁的线程调用此方法以便向队列中的下一个线程发出锁的信号。接收到脉冲后,等待线程就被移动到就绪队列中。在调用 Pulse 的线程释放锁后,就绪队列中的下一个线程(不一定是接收到脉冲的线程)将获得该锁。
另外:
Wait 和 Pulse 方法必须写在 Monitor.Enter 和Moniter.Exit 之间。