首页 理论教育 线程/进程安全性优化方案

线程/进程安全性优化方案

时间:2023-06-09 理论教育 版权反馈
【摘要】:一般来说,线程的安全性主要来源于其运行的并发性和对资源的共享性;进程的安全性主要在应用上,在于其对操作系统的威胁性。在某些项目中,经常会出现线程同步的问题,即多个线程在访问同一资源时,会出现安全问题。此时称系统处于死锁状态,这些永远在互相等待的线程称为死锁线程。

线程/进程安全性优化方案

进程和线程是两个范围不同的概念。进程是指程序在计算机上的一次执行活动。运行一个程序,相当于启动了一个进程。进程是操作系统进行资源分配的单位,通俗来说,它是一个正在执行的程序。

线程是进程中的一个实体,是被系统独立调度和分派的基本单位,它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。例如,一个在线播放软件,在播放歌曲时还可以进行下载,就可认为这两件工作由不同的线程完成。

多线程机制实际上相当于CPU交替分配给不同的代码段来运行,也就是说,某一个时间段,某线程运行,下一个时间段,另一个线程运行,各个线程都有抢占CPU的权利,至于决定哪个线程抢占,是操作系统需要考虑的事情。由于时间段的轮转非常快,用户感觉不出各个线程抢占CPU的过程,看起来好像计算机在“同时”做好几件事情。

一般来说,线程的安全性主要来源于其运行的并发性和对资源的共享性;进程的安全性主要在应用上,在于其对操作系统的威胁性。

1.线程同步安全

默认情况下,线程都是独立的,而且异步执行。线程中包含了运行时所需要的数据或方法,而不需要外部的资源或方法,也不必关心其他线程的状态或行为。但是在多个线程运行且共享数据的情况下,就需考虑其他线程的状态和行为,否则就不能保证程序运行结果的正确性。在某些项目中,经常会出现线程同步的问题,即多个线程在访问同一资源时,会出现安全问题。

所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其他线程也不能调用这个方法。通俗地讲,一个线程是否能够抢占CPU,必须考虑线程中的某种条件,而不能随便让操作系统按照默认方式分配CPU,如果条件不具备,就应该等待另一个线程运行,直到条件具备。

例如,有若干张飞机票,由两个线程去销售,要求没有票时能够提示:无票。采用Java语言以传统方法来编写代码。

这段程序看起来没有问题,但是它很不安全,并且这种不安全性很难被发现,会给项目后期维护带来较大的困难,同时付出较大的代价。观察程序中注释为代码行l处,当只剩下一张票时,线程1卖出了最后一张票,接着执行ticketNum--,但在ticketNum--还没来得及运行时,线程2有可能抢占CPU,来判断当前有无票可卖,此时,由于线程l还没有执行ticketNum--,当然票数还是1,线程2判断还可以卖票,这样,最后一张票卖出了两次。

如果让一个线程卖票时其他线程不能抢占CPU,可以给共享资源(这里为票)加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权访问该共享资源。现代的编程语言的设计思路都是把锁——同步标识加在代码段上,确切地说,是把同步标识放在“访问共享资源的代码段”上。

在Java语言中,synchronized关键字可以解决这个问题,整个语法形式表现为

注意,synchronized后的“同步锁对象”,必须是可以被各个线程共享的,如this、某个全局变量等,而不能是一个局部变量。其原理为:当某一线程运行同步代码段时,在“同步锁对象”上置一标记,运行完这段代码,标记消除。其他线程要想抢占CPU运行这段代码,必须在“同步锁对象”上先检查该标记,只有标记处于消除状态,才能抢占CPU。在上面的例子中,this是一个“同步锁对象”。

2.线程协作安全

多个线程合作完成一件事情的几个步骤,此时线程之间实现了协作。如一个工作需要若干个步骤,各个步骤都比较耗时,不能因为它们的运行,影响程序的运行效果,最好的方法就是将各步骤用线程来实现。但是,由于线程随时都有可能抢占CPU,因此可能在前面一个步骤没有完成时,后面的步骤线程就已经运行,该安全隐患造成系统得不到正确结果。

假定线程1负责完成一个复杂运算,计算1~1000各个数字的和,线程2负责得到结果并且写入数据库。采用Java语言以传统方法来编写代码。(www.xing528.com)

和前面的例子一样,它也是很不安全的,这种不安全性也很难被发现,也会给项目后期维护带来较大的困难。观察函数cal()中的代码,当线程thl运行后,线程th2运行,此时,线程th2随时可能抢占CPU,而不一定要等线程thl运行完毕。

解决方法:在一个线程运行时,其他线程必须等待该线程运行完毕,然后才能抢占CPU从而运行。在Java语言中,线程的join()方法可以解决这个问题。可在代码的thl.start()语句之后添加如下代码:

3.线程死锁安全

死锁(Deadlock)是指在两个或两个以上的线程执行过程中,因争夺资源而造成的一种互相等待的现象。此时称系统处于死锁状态,这些永远在互相等待的线程称为死锁线程。

产生死锁的4个必要条件如下:

1)互斥条件。资源每次只能被一个线程使用。如前面的“线程同步代码段”,就是典型的只能被一个线程使用的资源。

2)请求与保持条件。一个线程请求资源,但因为某种原因,该资源无法分配给它,于是该线程阻塞,此时,它对已获得的资源保持不放。

3)不剥夺条件。线程已获得的资源,在未使用完之前,不管其是否阻塞,无法强行剥夺。

4)循环等待条件。若干线程互相等待,形成一种头尾相接的循环等待资源关系。

这4个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

以Java语言为例,死锁一般来源于代码段的同步,当一段同步代码被某线程执行时,其他线程可能进入堵塞状态(无法抢占CPU),而刚好在该线程中,访问了某个对象,此时,除非同步锁定被解除,否则其他线程就不能访问那个对象。这可以称为“线程正在等待一个对象资源”。如果出现一种极端情况,一个线程等候某个对象,而这个对象又在等候下一个对象,依此类推;当这个“等候链”进入封闭状态,也就是说,最后那个对象等候的是第一个对象,此时,所有线程都会陷入无休止的相互等待状态,造成死锁。尽管这种情况并非经常出现,但是一旦碰到,程序的调试就变得异常艰难。在这里给出一个死锁的案例,代码如下:

这段程序的不安全性也很难被发现。观察函数run()中的代码,当thl运行后,进入代码段1,锁定了S1,如果此时th2运行,抢占CPU,进入代码段3,锁定S2,那么thl就无法运行代码段2,但是又没有释放S1,此时,th2也就不能运行代码段4,造成互相等待的现象。

就语言本身来说,尚未直接提供防止死锁的帮助措施,需要通过谨慎的设计来避免。一般情况下,主要是针对死锁产生的4个必要条件来进行破坏,从而避免和预防死锁。

以Java语言为例,Java并不提供对死锁的检测机制。但可以通过Java thread dump来进行判断:一般情况下,当死锁发生时,Java虚拟机处于挂起状态,thread dump可以给出静态稳定的信息,从操作系统上观察,虚拟机的CPU占用率为零,这时可以收集thread dump,查找“waiting for monitor entry”的线程,如果大量thread都在等待给同一个地址上锁,则说明很可能死锁发生了。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈