线程安全c语⾔,编写⾼效的线程安全类
在语⾔级⽀持锁定对象和线程间发信使编写线程安全类变得简单。本⽂使⽤简单的编程⽰例来说明开发⾼效的线程安全类是多么有效⽽直
观。
Java编程语⾔为编写多线程应⽤程序提供强⼤的语⾔⽀持。但是,编写有⽤的、没有错误的多线程程序仍然⽐较困难。本⽂试图概述⼏种
⽅法,程序员可⽤这⼏种⽅法来创建⾼效的线程安全类。
1.并发性
只有当要解决的问题需要⼀定程度的并发性时,程序员才会从多线程应⽤程序中受益。例如,如果打印队列应⽤程序仅⽀持⼀台打印机和⼀
台客户机,则不应该将它编写为多线程。⼀般说来,包含并发性的编码问题通常都包含⼀些可以并发执⾏的操作,同时也包含⼀些不可并发
执⾏的操作。例如,为多个客户机和⼀个打印机提供服务的打印队列可以⽀持对打印的并发请求,但向打印机的输出必须是串⾏形式的。多
线程实现还可以改善交互式应⽤程序的响应时间。
2.Synchronized关键字
虽然多线程应⽤程序中的⼤多数操作都可以并⾏进⾏,但也有某些操作(如更新全局标志或处理共享⽂件)不能并⾏进⾏。在这些情况下,必
须获得⼀个锁来防⽌其他线程在执⾏此操作的线程完成之前访问同⼀个⽅法。在Java程序中,这个锁是通过synchronized关键字提供
的。清单1说明了它的⽤法。
清单1.使⽤synchronized关键字来获取锁
publicclassMaxScore{
intmax;
publicMaxScore(){
max=0;
}
publicsynchronizedvoidcurrentScore(ints){
if(s>max){
max=s;
}
}
publicintmax(){
returnmax;
}
}
这⾥,两个线程不能同时调⽤currentScore()⽅法;当⼀个线程⼯作时,另⼀个线程必须阻塞。但是,可以有任意数量的线程同时通过
max()⽅法访问最⼤值,因为max()不是同步⽅法,因此它与锁定⽆关。
试考虑在MaxScore类中添加另⼀个⽅法的影响,该⽅法的实现如清单2所⽰。
清单2.添加另⼀个⽅法
publicsynchronizedvoidret(){
max=0;
}
这个⽅法(当被访问时)不仅将阻塞ret()⽅法的其他调⽤,⽽且也将阻塞MaxScore类的同⼀个实例中的currentScore()⽅法,因为这
两个⽅法都访问同⼀个锁。如果两个⽅法必须不彼此阻塞,则程序员必须在更低的级别使⽤同步。清单3是另⼀种情况,其中两个同步的
⽅法可能需要彼此独⽴。
清单3.两个独⽴的同步⽅法
.*;
publicclassJury{
Vectormembers;
Vectoralternates;
publicJury(){
members=newVector(12,1);
alternates=newVector(12,1);
}
publicsynchronizedvoidaddMember(Stringname){
(name);
}
publicsynchronizedvoidaddAlt(Stringname){
(name);
}
publicsynchronizedVectorall(){
Vectorretval=newVector(members);
(alternates);
returnretval;
}
}
此处,两个不同的线程可以将members和alternates添加到Jury对象中。请记住,synchronized关键字既可⽤于⽅法,更⼀般地,
也可⽤于任何代码块。清单4中的两段代码是等效的。
清单4.等效的代码
synchronizedvoidf(){voidf(){
//执⾏某些操作
synchronized(this){
}//执⾏某些操作
}
}
所以,为了确保addMember()和addAlt()⽅法不彼此阻塞,可按清单5所⽰重写Jury类。
清单5.重写后的Jury类
.*;
publicclassJury{
Vectormembers;
Vectoralternates;
publicJury(){
members=newVector(12,1);
alternates=newVector(12,1);
}
publicvoidaddMember(Stringname){
synchronized(members){
(name);
}
}
publicvoidaddAlt(Stringname){
synchronized(alternates){
(name);
}
}
publicVectorall(){
Vectorretval;
synchronized(members){
retval=newVector(members);
}
synchronized(alternates){
(alternates);
}
returnretval;
}
}
请注意,我们还必须修改all()⽅法,因为对Jury对象同步已没有意义。在改写后的版本中,addMember()、addAlt()和all()⽅法只访
问与members和alternates对象相关的锁,因此锁定Jury对象毫⽆⽤处。另请注意,all()⽅法本来可以写为清单6所⽰的形式。
清单6.将members和alternates⽤作同步的对象
publicVectorall(){
synchronized(members){
synchronized(alternates){
Vectorretval;
retval=newVector(members);
(alternates);
}
}
returnretval;
}
但是,因为我们早在需要之前就获得members和alternates的锁,所以这效率不⾼。清单5中的改写形式是⼀个较好的⽰例,因为它只
在最短的时间内持有锁,并且每次只获得⼀个锁。这样就完全避免了当以后增加代码时可能产⽣的潜在死锁问题。
3.同步⽅法的分解
正如在前⾯看到的那样,同步⽅法获取对象的⼀个锁。如果该⽅法由不同的线程频繁调⽤,则此⽅法将成为瓶颈,因为它会对并⾏性造成限
制,从⽽会对效率造成限制。这样,作为⼀个⼀般的原则,应该尽可能地少⽤同步⽅法。尽管有这个原则,但有时⼀个⽅法可能需要完成需
要锁定⼀个对象⼏项任务,同时还要完成相当耗时的其他任务。在这些情况下,可使⽤⼀个动态的“锁定-释放-锁定-释放”⽅法。例如,清
单7和清单8显⽰了可按这种⽅式变换的代码。
清单7.最初的低效率代码
publicsynchonizedvoiddoWork(){
unsafe1();
write_file();
unsafe2();
}
清单8.重写后效率较⾼的代码
publicvoiddoWork(){
synchonized(this){
unsafe1();
}
write_file();
synchonized(this){
unsafe2();
}
}
清单7和清单8假定第⼀个和第三个⽅法需要对象被锁定,⽽更耗时的write_file()⽅法不需要对象被锁定。如您所见,重写此⽅法以
后,对此对象的锁在第⼀个⽅法完成以后被释放,然后在第三个⽅法需要时重新获得。这样,当write_file()⽅法执⾏时,等待此对象的锁
的任何其他⽅法仍然可以运⾏。将同步⽅法分解为这种混合代码可以明显改善性能。但是,您需要注意不要在这种代码中引⼊逻辑错误。
4.嵌套类
内部类在Java程序中实现了⼀个令⼈关注的概念,它允许将整个类嵌套在另⼀个类中。嵌套类作为包含它的类的⼀个成员变量。如果定期
被调⽤的的⼀个特定⽅法需要⼀个类,就可以构造⼀个嵌套类,此嵌套类的唯⼀任务就是定期调⽤所需的⽅法。这消除了对程序的其他部分
的相依性,并使代码进⼀步模块化。清单9,⼀个图形时钟的基础,使⽤了内部类。
清单9.图形时钟⽰例
publicclassClock{
protectedclassRefresherextendsThread{
intrefreshTime;
publicRefresher(intx){
super(“Refresher”);
refreshTime=x;
}
publicvoidrun(){
while(true){
try{
sleep(refreshTime);
}
catch(Exceptione){}
repaint();
}
}
}
publicClock(){
Refresherr=newRefresher(1000);
();
}
privatevoidrepaint(){
//获取时间的系统调⽤
//重绘时钟指针
}
}
清单9中的代码⽰例不靠任何其他代码来调⽤repaint()⽅法。这样,将⼀个时钟并⼊⼀个较⼤的⽤户界⾯就相当简单。
5.事件驱动处理
当应⽤程序需要对事件或条件(内部的和外部的)作出反映时,有两种⽅法或⽤来设计系统。
在第⼀种⽅法(称为轮询)中,系统定期确定这⼀状态并据此作出反映。这种⽅法(虽然简单)也效率不⾼,因为您始终⽆法预知何时需要调⽤
它。
第⼆种⽅法(称为事件驱动处理)效率较⾼,但实现起来也较为复杂。在事件驱动处理的情况下,需要⼀种发信机制来控制某⼀特定线程何时
应该运⾏。在Java程序中,您可以使⽤wait()、notify()和notifyAll()⽅法向线程发送信号。这些⽅法允许线程在⼀个对象上阻塞,直到
所需的条件得到满⾜为⽌,然后再次开始运⾏。这种设计减少了CPU占⽤,因为线程在阻塞时不消耗执⾏时间,并且可在notify()⽅法被
调⽤时⽴即唤醒。与轮询相⽐,事件驱动⽅法可以提供更短的响应时间。
6.创建⾼效的线程安全类的步骤
编写线程安全类的最简单的⽅法是⽤synchronized声明每个⽅法。虽然这种⽅案可以消除数据损坏,但它同时也会消除您预期从多线程获
得的任何收益。这样,您就需要分析并确保在synchronized块内部仅占⽤最少的执⾏时间。您必须格外关注访问缓慢资源(⽂件、⽬录、
⽹络套接字和数据库)的⽅法,这些⽅法可能降低您的程序的效率。尽量将对这类资源的访问放在⼀个单独的线程中,最好在任何
synchronized代码之外。
⼀个线程安全类的⽰例被设计为要处理的⽂件的中⼼储存库。它与使⽤getWork()和finishWork()与WorkTable类对接的⼀组线程⼀起
⼯作。本例旨在让您体验⼀下全功能的线程安全类,该类使⽤了helper线程和混合同步。请注意继续添加要处理的新⽂件的Refresher
helper线程的⽤法。本例没有调整到最佳性能,很明显有许多地⽅可以改写以改善性能,⽐如将Refresher线程改为使⽤wait()/notify()
⽅法事件驱动的,改写populateTable()⽅法以减少列出磁盘上的⽂件(这是⾼成本的操作)所产⽣的影响。
7.⼩结
通过使⽤可⽤的全部语⾔⽀持,Java程序中的多线程编程相当简单。但是,使线程安全类具有较⾼的效率仍然⽐较困难。为了改善性能,
您必须事先考虑并谨慎使⽤锁定功能。
本文发布于:2023-01-04 02:31:31,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/88068.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |