扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
public class ThreadingExample extends Object { public static void main( String args[] ) { Thread[] threads = new Thread[2]; for( int count=1;count<=threads.length;count ) { threads[count] = new Thread( new Runnable() { public void run() { count(); } } ); threads[count].start(); } } public static void count() { for( int count=1;count<=5;count ) System.out.print( count " " ); } } |
我们可以使用System.Threading.Thread和System.Threading.ThreadStart二个类将上述的Java程序转换为C#语言:
using System.Threading; public class ThreadingExample : Object { public static void Main() { Thread[] threads = new Thread[2]; for( int count=1;count<=threads.Length;count ) { threads[count] = new Thread( new ThreadStart( Count ) ); threads[count].Start(); } } public static void Count() { for( int count=1;count<=5;count ) Console.Write( count " " ); } } |
这个例子中有一些小技巧。Java允许扩展java.lang.Thread类和执行java.lang.Runnable接口,C#则没有为我们提供这些便利。一个C#中的Thread对象是不可知的,必须通过ThreadStart进行创建,这意味着不能使用内部的类模式,而必须创建一个对象,而且必须传递给线程一个对象的方法供线程执行用。
线程的使用
Java中存在许多编程人员希望能够对线程使用的标准操作:例如,测试线程是否存在、加入一个线程直到它死亡、杀死一个线程等。
表1:线程管理的函数
Java中java.lang.Thread中的方法和C#中System.Threading.Thread对象的对比。
setDaemon( boolean on) 方法
IsBackground 设置属性值
使一个存在的进程成为一个新线程(如果剩下的所有进程都成了新线程,程序将停止运行)。
isDaemon()方法
IsBackground 获取属性
如果该线程是一个后台线程,则返回真值。
isAlive() 方法
IsAlive 获取属性
如果该线程处于活动状态,则返回真值。
interrupt() 方法
Interrupt() 方法
尽管在Java中这一方法可以用来设置线程的中断状态,而且可以用来检查线程是否被中断。在C#中没有相应的方法,对一个没有处于阻塞状态的线程执行Interrupt方法将使下一次阻塞调用自动失效。
isInterrupted() 方法
n/a
如果该线程处于阻塞状态,则返回真值。
sleep( long millis )和sleep( long millis, int nanos )
Sleep( int millisecondTimeout ) and Sleep( System.TimeSpan )方法
使正在执行的线程暂停一段给定的时间,或直到它被中断。这一方法将在Java中将产生一个java.lang.InterruptedException状态,在C#中将产生System.Threading. ThreadInterruptedException状态。
join()、join( long millis )和join( long millis, int nanos ) 方法
Join()、Join( int millisecondTimeout )和Join( System.TimeSpan ) 方法 与Java中仅依靠超时设定不同的是,在C#语言中则依据线程停止运行是由于线程死亡(返回真)或是超时(返回假)而返回一个布尔型变量。
suspend() 方法
Suspend() 方法
二者的功能相同。这一方法容易引起死循环,如果一个占有系统关健资源的线程被挂起来,则在这一线程恢复运行之前,其他的线程不能访问该资源。
resume() 方法
Resume() 方法
恢复一个被挂起的线程。
stop() 方法
Abort() 方法
参见下面的“线程停止”部分。
(特别说明,在上面的表中,每个小节的第一行是java中的方法,第二行是C#中的方法,第三行是有关的注释,由于在文本文件中不能组织表格,请编辑多费点心组织表格,原文中有表格的格式。)
线程的中止
由于能够在没有任何征兆的情况下使运行的程序进入一种混乱的状态,Java中的Thread.stop受到了普遍的反对。根据所调用的stop()方法,一个未经检查的java.lang.ThreadDeath错误将会破坏正在运行着的程序的栈,随着它的不断运行,能够解除任何被锁定的对象。由于这些锁被不分青红皂白地被打开,由它们所保护的数据就非常可能陷入混乱状态中。
根据当前的Java文档,推荐的中止一个线程的方法是让运行的线程检查一个由其他的线程能够改变的变量,该变量代表一个“死亡时间”条件。下面的程序就演示了这种方法。
// 条件变量 private boolean timeToDie = false; // 在每次迭代中对条件变量进行检查。 class StoppableRunnable extends Runnable { public void run() { while( !timeToDie ) { // 进行相应的操作 } } } |
线程的同步
从概念上来看,线程非常易于理解,实际上,由于他们可能交互地对同一数据结构进行操作,因此它们成为了令编程人员头疼的一种东西。以本文开始的ThreadingExample为例,当它运行时,会在控制台上输出多种不同的结果。从 1 2 3 4 5 1 2 3 4 5到 1 1 2 2 3 3 4 4 5 5或 1 2 1 2 3 3 4 5 4 5在内的各种情况都是可能出现的,输出结果可能与操作系统的线程调度方式之间的差别有关。有时,需要确保只有一个线程能够访问一个给定的数据结构,以保证数据结构的稳定,这也是我们需要线程同步机制的原因所在。
为了保证数据结构的稳定,我们必须通过使用“锁”来调整二个线程的操作顺序。二种语言都通过对引用的对象申请一个“锁”,一旦一段程序获得该“锁”的控制权后,就可以保证只有它获得了这个“锁”,能够对该对象进行操作。同样,利用这种锁,一个线程可以一直处于等待状态,直到有能够唤醒它信号通过变量传来为止。
表2:线程同步
需要对线程进行同步时需要掌握的关健字
synchronized
lock
C#中的lock命令实际上是为使用System.Threading.Monitor类中的Enter和Exit方法的语法上的准备
Object.wait()
Monitor.Wait( object obj )
C#中没有等待对象的方法,如果要等待一个信号,则需要使用System.Threading.Monitor类,这二个方法都需要在同步的程序段内执行。
Object.notify()
Monitor.Pulse( object obj )
参见上面的Monitor.Wait的注释。
Object.notify()
Monitor.PulseAll( object obj )
参见上面的Monitor.Wait的注释。
(特别说明,在上面的表中,每个小节的第一行是java中的方法,第二行是C#中的方法,第三行是有关的注释,由于在文本文件中不能组织表格,请编辑多费点心组织表格,原文中有表格的格式。)
我们可以对上面的例子进行一些适当的修改,通过首先添加一个进行同步的变量,然后对count()方法进行如下的修改,使变量在“锁”中被执行加1操作。
public static Object synchronizeVariable = "locking variable"; public static void count() { synchronized( synchronizeVariable ) { for( int count=1;count<=5;count ) { System.out.print( count " " ); synchronizeVariable.notifyAll(); if( count < 5 ) try { synchronizeVariable.wait(); } catch( InterruptedException error ) { } } } } |
作了上述的改变后,每次只有一个线程(因为一次只能有一个线程获得synchronizeVariable)能够执行for loop循环输出数字1;然后,它会唤醒所有等待synchronizeVariable的线程(尽管现在还没有线程处于等待状态。),并试图获得被锁着的变量,然后等待再次获得锁变量;下一个线程就可以开始执行for loop循环输出数字1,调用notifyAll()唤醒前面的线程,并使它开始试图获得synchronizeVariable变量,使自己处于等待状态,释放synchronizeVariable,允许前面的线程获得它。这个循环将一直进行下去,直到它们都输出完从1到5的数字。
通过一些简单的语法变化可以将上述的修改在C#中实现:
public static Object synchronizeVariable = "locking variable"; public static void count() { lock( synchronizeVariable ) { for( int count=1;count<=5;count ) { System.out.print( count " " ); Monitor.PulseAll( synchronizeVariable ); if( count < 5 ) Monitor.Wait( synchronizeVariable ); } } } |
public static int x = 1; public static void increment() { x = x 1; } |
如果有二个不同的线程同时调用increment(),x最后的值可能是2或3,发生这种情况的原因可能是二个进程无序地访问x变量,在没有将x置初值时对它执行加1操作;在任一线程有机会对x执行加1操作之前,二个线程都可能将x读作1,并将它设置为新的值。
在Java和C#中,我们都可以实现对x变量的同步访问,所有进程都可以按各自的方式运行。但通过使用Interlocked类,C#提供了一个对这一问题更彻底的解决方案。Interlocked类有一些方法,例如Increment( ref int location )、Decrement( ref int location ),这二个方法都取得整数型参数,对该整数执行加或减1操作,并返回新的值,所有这些操作都以“不可分割的”方式进行,这样就无需单独创建一个可以进行同步操作的对象,如下例所示:
public static Object locker = ... public static int x = 1; public static void increment() { synchronized( locker ) { x = x 1; } } |
C#中的Interlocked类可以用下面的代码完成相同的操作:
public static int x = 1; public static void Increment() { Interlocked.Increment( ref x ); } |
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者