线程是允许进行并行计算的一个抽象概念在另一个线程完成计算任务的同时一个线程可以对图像进行更新二个线程可以同时处理同一个进程发出的二个网络请求我们在这篇文章中将重点讨论Java和C#在线程方面的不同之处并将一些Java中线程的常用模式转换为C#
从概念上讲线程提供了一种在一个软件中并行执行代码的方式━━每个线程都同时在一个共享的内存空间中执行指令(当然是在一个处理器上这是通过处于运行状态的线程的交替执行完成的)因此每个线程都可以访问一个程序内的数据结构由于这种原因多线程编程的难度就可想而知了因为一个程序内有许多不同的线程需要安全地共享数据
线程的创建和运行
Java在javalangThread和javalangRunnable类中提供了大部分的线程功能创建一个线程非常简单就是扩展Thread类并调用start()通过创建一个执行Runnable()的类并将该类作为参数传递给Thread()也可以定义一个线程仔细地阅读下面这个简单的Java程序其中有个线程同时在从数到并将结果打印出来
public class ThreadingExample
extends Object {
public static void main( String args[] ) {
Thread[] threads = new Thread[];
for( int count=;count<=threadslength;count ) {
threads[count] = new Thread( new Runnable() {
public void run() {
count();
}
} );
threads[count]start();
}
}
public static void count() {
for( int count=;count<=;count )
Systemoutprint( count );
}
}
我们可以使用SystemThreadingThread和SystemThreadingThreadStart二个类将上述的Java程序转换为C#语言
using SystemThreading;
public class ThreadingExample : Object {
public static void Main() {
Thread[] threads = new Thread[];
for( int count=;count<=threadsLength;count ) {
threads[count] = new Thread( new ThreadStart( Count ) );
threads[count]Start();
}
}
public static void Count() {
for( int count=;count<=;count )
ConsoleWrite( count );
}
}
这个例子中有一些小技巧Java允许扩展javalangThread类和执行javalangRunnable接口C#则没有为我们提供这些便利一个C#中的Thread对象是不可知的必须通过ThreadStart进行创建这意味着不能使用内部的类模式而必须创建一个对象而且必须传递给线程一个对象的方法供线程执行用
线程的使用
Java中存在许多编程人员希望能够对线程使用的标准操作例如测试线程是否存在加入一个线程直到它死亡杀死一个线程等
表线程管理的函数
Java中javalangThread中的方法和C#中SystemThreadingThread对象的对比
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( SystemTimeSpan )方法
使正在执行的线程暂停一段给定的时间或直到它被中断这一方法将在Java中将产生一个javalangInterruptedException状态在C#中将产生SystemThreading ThreadInterruptedException状态
join()join( long millis )和join( long millis int nanos ) 方法
Join()Join( int millisecondTimeout )和Join( SystemTimeSpan ) 方法 与Java中仅依靠超时设定不同的是在C#语言中则依据线程停止运行是由于线程死亡(返回真)或是超时(返回假)而返回一个布尔型变量
suspend() 方法
Suspend() 方法
二者的功能相同这一方法容易引起死循环如果一个占有系统关健资源的线程被挂起来则在这一线程恢复运行之前其他的线程不能访问该资源
resume() 方法
Resume() 方法
恢复一个被挂起的线程
stop() 方法
Abort() 方法
参见下面的线程停止部分
(特别说明在上面的表中每个小节的第一行是java中的方法第二行是C#中的方法第三行是有关的注释由于在文本文件中不能组织表格请编辑多费点心组织表格原文中有表格的格式)
线程的中止
由于能够在没有任何征兆的情况下使运行的程序进入一种混乱的状态Java中的Threadstop受到了普遍的反对根据所调用的stop()方法一个未经检查的javalangThreadDeath错误将会破坏正在运行着的程序的栈随着它的不断运行能够解除任何被锁定的对象由于这些锁被不分青红皂白地被打开由它们所保护的数据就非常可能陷入混乱状态中
根据当前的Java文档推荐的中止一个线程的方法是让运行的线程检查一个由其他的线程能够改变的变量该变量代表一个死亡时间条件下面的程序就演示了这种方法
// 条件变量
private boolean timeToDie = false;
// 在每次迭代中对条件变量进行检查
class StoppableRunnable
extends Runnable {
public void run() {
while( !timeToDie ) {
// 进行相应的操作
}
}
}
上述的讨论对C#中的Abort方法也适合根据调用的Abort方法令人捉摸不定的SystemThreadingThreadAbortException可能会破坏线程的栈它可能释放线程保持的一些变量使处于保护状态中的数据结构出现不可预测的错误我建议使用与上面所示的相似的方法来通知一个应该死亡的线程
线程的同步
从概念上来看线程非常易于理解实际上由于他们可能交互地对同一数据结构进行操作因此它们成为了令编程人员头疼的一种东西以本文开始的ThreadingExample为例当它运行时会在控制台上输出多种不同的结果从 到 或 在内的各种情况都是可能出现的输出结果可能与操作系统的线程调度方式之间的差别有关有时需要确保只有一个线程能够访问一个给定的数据结构以保证数据结构的稳定这也是我们需要线程同步机制的原因所在
为了保证数据结构的稳定我们必须通过使用锁来调整二个线程的操作顺序二种语言都通过对引用的对象申请一个锁一旦一段程序获得该锁的控制权后就可以保证只有它获得了这个锁能够对该对象进行操作同样利用这种锁一个线程可以一直处于等待状态直到有能够唤醒它信号通过变量传来为止
表线程同步
需要对线程进行同步时需要掌握的关健字
synchronized
lock
C#中的lock命令实际上是为使用SystemThreadingMonitor类中的Enter和Exit方法的语法上的准备
Objectwait()
MonitorWait( object obj )
C#中没有等待对象的方法如果要等待一个信号则需要使用SystemThreadingMonitor类这二个方法都需要在同步的程序段内执行
Objectnotify()
MonitorPulse( object obj )
参见上面的MonitorWait的注释
Objectnotify()
MonitorPulseAll( object obj )
参见上面的MonitorWait的注释
(特别说明在上面的表中每个小节的第一行是java中的方法第二行是C#中的方法第三行是有关的注释由于在文本文件中不能组织表格请编辑多费点心组织表格原文中有表格的格式)
我们可以对上面的例子进行一些适当的修改通过首先添加一个进行同步的变量然后对count()方法进行如下的修改使变量在锁中被执行加操作
public static Object synchronizeVariable = locking variable;
public static void count() {
synchronized( synchronizeVariable ) {
for( int count=;count<=;count ) {
Systemoutprint( count );
synchronizeVariablenotifyAll();
if( count < )
try {
synchronizeVariablewait();
} catch( InterruptedException error ) {
}
}
}
}
作了上述的改变后每次只有一个线程(因为一次只能有一个线程获得synchronizeVariable)能够执行for loop循环输出数字然后它会唤醒所有等待synchronizeVariable的线程(尽管现在还没有线程处于等待状态)并试图获得被锁着的变量然后等待再次获得锁变量下一个线程就可以开始执行for loop循环输出数字调用notifyAll()唤醒前面的线程并使它开始试图获得synchronizeVariable变量使自己处于等待状态释放synchronizeVariable允许前面的线程获得它这个循环将一直进行下去直到它们都输出完从到的数字
通过一些简单的语法变化可以将上述的修改在C#中实现
public static Object synchronizeVariable = locking variable;
public static void count() {
lock( synchronizeVariable ) {
for( int count=;count<=;count ) {
Systemoutprint( count );
MonitorPulseAll( synchronizeVariable );
if( count < )
MonitorWait( synchronizeVariable );
}
}
}
C#中特有的线程功能
象我们一直对C#所抱的期望那样C#中确实有一些Java不支持的方法类和函数对于铁桿的Java线程编程人员而言这可是一件好事因为他们可以用C#编写代码然后在Java代码中引用
Enter/TryEnter/Exit
要在Java中获得某一变量的锁必须在代码的首尾二端加上synchronized关健字指明需要获得锁的对象一旦线程开始执行synchronized块中的代码它就获得了对这一对象的锁的控制权同样一旦线程已经离开了synchronized块它也将释放这一对象的锁我们已经知道C#也有一个相似的被称作lock的关健字除了lock这个关健字外C#还提供了内置的获得和释放锁的方法MonitorEnter( object obj )和 MonitorExit( object obj )通过使用这些方法编程人员可以获得与使用lock相同的作用但提供了更精确的控制方法例如可以在一个方法中锁定几个变量而不同时或在代码中的不同部分释放它们
对一个需要进行同步的对象执行SystemThreadingMonitorEnter操作将使线程获得该对象的锁或者在由其他线程控制着该对象的锁时进行阻塞通过执行MonitorExit方法就可以释放锁如果线程已经不控制着该对象的锁了这一方法将会产生一个SystemThreadingSynchronizationLockException异常信号
C#中的Monitor类不但包括Enter方法还包括TryEnter方法如果执行该方法就会或者获得一个锁或者返回一个表明它不能获得锁的返回值
原子操作
SystemThreadingInterlocked类提供了程序对由几个线程共享的变量进行同步访问的能力C#把一些操作抽象为原子操作或不可分割的操作为了说明这一问题是如何解决的我们来看一下下面的Java代码
public static int x = ;
public static void increment() {
x = x ;
}
如果有二个不同的线程同时调用increment()x最后的值可能是或发生这种情况的原因可能是二个进程无序地访问x变量在没有将x置初值时对它执行加操作在任一线程有机会对x执行加操作之前二个线程都可能将x读作并将它设置为新的值
在Java和C#中我们都可以实现对x变量的同步访问所有进程都可以按各自的方式运行但通过使用Interlocked类C#提供了一个对这一问题更彻底的解决方案Interlocked类有一些方法例如Increment( ref int location )Decrement( ref int location )这二个方法都取得整数型参数对该整数执行加或减操作并返回新的值所有这些操作都以不可分割的方式进行这样就无需单独创建一个可以进行同步操作的对象如下例所示
public static Object locker =
public static int x = ;
public static void increment() {
synchronized( locker ) {
x = x ;
}
}
C#中的Interlocked类可以用下面的代码完成相同的操作
public static int x = ;
public static void Increment() {
InterlockedIncrement( ref x );
}
Interlocked中还包括一个名字为Exchange的方法可以不可分割地将一个变量的值设置为另一个变量的值
线程池
如果许多利用了线程的应用软件都创建线程这些线程将会因等待某些条件(键盘或新的I/O输入等)而在等待状态中浪费大部分的时间C#提供的SystemThreadingThreadPool对象可以解决这一问题使用ThreadPool和事件驱动的编程机制程序可以注册一个SystemThreadingWaitHandle对象(WaitHandle是C#编程中等待和通知机制的对象模型)和SystemThreadingWaitOrTimerCallback对象所有的线程无需自己等待WaitHandle的释放ThreadPool将监控所有向它注册的WaitHandle然后在WaitHandle被释放后调用相应WaitOrTimerCallback对象的方法
结束语
在本篇文章中我们简单地讨论了C#提供的用于线程和并行操作的机制其中的大部分与Java相似━━C#提供了可以运行提供的方法的Thread对象同时提供了对代码访问进行同步的方法与在其他方面一样C#在线程方面也提供了一些Java不支持的语法(在一定程度上揭示了同步操作的一些底层的内容)Java编程人员可能会发现这一部分非常有用