admin 管理员组文章数量: 1184232
2024年3月19日发(作者:jsswitch编程入门书籍)
应用
il一一
科学
Java
多线程编程的探究
刘翠焕司海峰李素朵
(石家庄法商职业学院河北石家庄050091)
[摘要】通过对'Java多线稃编程的研究,得出了如何灵活、正确的使用多线程编程提高整个应用系统的性能,同时对在使用多线程编程中容易出现的问题,也
做出了提示,希望能对编程者有所帮助。
【关键词】线程优先级
中圈分类号:TP3
同步阻塞
文章编号;1671--7597(2--02文献标识码:^
线程本是操作系统的一个重要概念。多线程是指程序中同时存在着多
个执行体,他们按几条不同的执行线路共同工作,独立完成各自的功能而
互不干扰。使用多线程技术可以使系统同时运行多个执行体,减少程序的
响应时间,提高计算机资源的使用效率。正确的使用多线程技术可以提高
整个应用系统的性能.而多线程机制也是Java语言的一个重要特征。
一、如何在Java中宴现多线程
在Java中,创建线程有两种方法:一是通过创建Thread二类的子类来实
现,二是通过实现Runnable:接口的类来实现。下面我们就这来那各种方法
展开讨论。
(一)创建!Thread:类的子类
Thread类最重要的方法是run0。它为Thread类的方法start0
所调用,提供我们的线程所要执行的代码。为了指定我们自己的代码,只
需要覆盖它l
我们在创建的Thread类的子类中重写run(),加入线程所要执行
的代码即可。下面是一个例子:
public
class
MyThread
extendsThread
{
String
threadNeme:
intsecond:
。
int
times:
public
MyThread(Stringneme,int
x,int
m)
{
threadName=neme:
second=x:
times=m;
public
void
run0
{
try{
for(inti:o:i {System out.print II.threadNeme): Sleep ICsecond):} SysteL out.println(。线程”+threadNeme+。结束!”): catch(InterruptedException e) .{System.out.println(e): } } public static voidmain(String argsU) (’Mythreadthreada=new Mythread(“A”,100,20。J’: Mythread threadb=new Mythread(。B”,200,20): threada.start 0: threadh.start 0: } 这种法简单明了,符合大家的习惯,但是,它也有一个很大的缺 点,那就是如果我们的类已经从一个类继承(如小程序必须继承自Applet 类),则无法再继承Thread类,这时如果我们又不想建立一个新的类, 应该怎么办呢? 我们不妨来探索一种新的方法:我们不创建Thread类的子类,而是 直接使用它,那么我们只能将我们的方法作为参数传递给Thread类的实 例,有点类似回调函数。但是3ava没有指针,我们只能传递一个包含这 个方法的类的实例。那么如何限制这个类必须包含这一方法呢?当然是使 用接口l(虽然抽象类也可满足,但是需要继承。而我们之所以要采用这 种新方法,不就是为了避免继承带来的限制吗?) (二)通过实现!RunnabIe接口的类 Runnable接口只有一个方法run(),我们声明自己的类实现Runnable 接口并提供这一方法,将我们的线程代码写入其中,就完成了这一部分的 任务。但是Runnable:接口并没有任何对线程的支持,我们还必须创建 Thread.类的实例,这一点通过Thread类的构造函数public Thread( Runnable target)来实现。下面是一个例子: public class坶Thread implements Runnable {int count:l, number: publ ic MyThread(int nul) number 5 hem; System。out.println(。创建线程 ’ + number)]: } pubiic void run(]} whi le(true) System,out.println(’线程’+number+’:计数’+count): if(++count=6)return; } public static void main(『'。String args[]) { for(int=0: i< 5; i++) new Thread(new MyThread(i+1)).start 0: } } 严格地说,创建!Thread子类的实例也是可行的,但是必须注意的是, 该子类必须没有覆盖1'hread类的run方法。否则该线程执行的将是子类 的run方法,而不是我们用以实现,Runnablej凄口的类的run方法,对此大家 不妨试验一下。 使用Runnable接口来实现多线程使得我们能够在一个类中包容所有的 代码,有利于封装,它的缺点在于.我们只能使用一套代码,若想创建多 个线程并使各个线程执行不同的代码,则仍必须额外创建类,如果这样的 话,在大多数情况下也许还不如直接用多个类分别继承Thread来得紧凑。 :、缀程的优先曩 线程的优先级代表该线程的重要程度.当有多个线程同时处于可执行 状态井等待获得CPU时间时,线程调度系统根各个线程的优先级来决定给 谁分配CPU时间,优先级高的线程有更大的机会获褶LCPU时间,优先级低的 线程也不是没有机会,只是机会要小一些罢了。 回 翥霎Ⅵ裂器一㈧ 可以调用Thread类的方法getPriority0和setPriority()来存取线程 的优先级。线程的优先级界于1(MIN_PRIORITY)和lO(1IAxPRIORITY)之间, 缺省是5(NORM PRIORITY). 兰、媛程的同步 由于同一进程的多个线程共享同~片存储空间,在带来方便的同时, 也带来了访问冲突这个严重的问题。Java中引入了“互斥锁”的标记,保 证任一时刻只能一个线程访问数据。synchronized关键字与互斥锁联系, 它包括两种用法:synchronized方法和synchronized块。 (--)synchronized方法 通过在方法声明中加入synchronized关键字来声明synchronized 方法。如:public synchronizedvoid accessVal(intnewVal): Synchronized方法控制对类成员变量的访问:每个类实例对应一把 锁,每个synchronized方法都必须获得调用该方法的类实例的锁方能执 行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回 时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。 这种机制确保了同一时刻对于每一个类实例,其所有声明为synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得 该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有 可能访问类成员变量的方法均被声明为synchronized)。 在Java中,不光是类实例,每一个类也对应一把锁,这样我们也可将 类的静态成员函数声明为synchronized,以控制其对类的静态成员变量的 访问. synchronized方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法run()声明为synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized方法的调用都永远不会成功。当然我们可以通过将访问类成 员变量的代码放到专门的方法中,将其声明为synchronized,并在主方法 中调用来解决这一问题,但是Java为我们提供了更好的懈决办法,那就是 synchronized块。 (二)synchronized块 通过synchronized关键字来声明synchronized块。语法如下: synchronized(syncObject)f//允许访问控制的代码} synchronized块是这样一个代码块,其中的代码必须获得对象 syncObject(如前所述,可以是类实例或类)的锁方能执行,具体机制同 前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性 较高。 四、线程的阻塞 为了解决对共享存储区的访问冲突,Java引入了同步机制,现在让 我们来考察多个线程对共享资源的访问,显然同步机制已经不够了,因为 在任意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻 准备好了的资源也可能不止一个。为了解决这种情况下的访问控制问题, Java引入了对阻塞机制的支持。Java提供了大量方法来支持阻塞。 (一)SIeep0方法 sleep0允许指定以毫秒为单位的一段时间作为参数,它使得线程在 指定的时间内进入阻塞状态,不能得到CPU时间,指定的时间一过,线程 重新进入可执行状态。 典型地,sleep()被用在等待某个资源就绪的情形:测试发现条件不 满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。 (二)suspend0和reeume0方法 两个方法配套使用,suspend0使得线程进入阻塞状态,并且不会自 动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状 态.典型地,suspend()和resume()被用在等待另一个线程产生的结果的情 形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果 后,调用resume()使其恢复。 (--)Yiefd0方法 Yield()使得线程放弃当前分得的CPU时间,但是不使线程阻塞,即线 程仍处于可执行状态,随时可能再次分得CPU时间.调用yield0的效果等 价于调度程序认为该线程已执行了足够的时间从而转到另一个线程. 回 (四)wait0和notify0方法 两个方法配套使用,wait0使得线程进入阻塞状态,它有两种形式, 一种允许指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者 当对应的notify0被调用或者超出指定时间时线程重新进入可执行状态, 后者则必须对应的notify0被调用. (1)关于wait0和notify()方法两点说明:第一l调用notify0 方法导致解除阻塞的线程是从因调用该对象的wait0方法而阻塞的线程中 随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小 心,避免因这种不确定性而产生问题。第二:除了notify(),还有一个方 法notifyAll()也可起到类似作用,唯一的区别在于,调用notifyAll()方 法将把因调用该对象的wait0方法而阻塞的所有线程一次性全部解除阻 塞。当然,只有获得锁的那一个线程才能进入可执行状态. (2)wait0和notify0方法的特点:初看起来它们与suspend()和 resume0方法对没有什么分别,但是事实上它们是截然不同的。区别的核 心在于。前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了 的话),而这一对方法则相反。 首先,前面叙述的所有方法都隶属于Thread类,但是这一对方法却 直接隶属于Object类,也就是说,所有对象都拥有这一对方法。 其次,前面叙述的所有方法都可在任何位置调用,但是这一对方法却 必须在synchronized方法或块中调用。因为只有在synchronized方法或 块中当前线程才占有锁,才有锁可以释放。 wait()和notify()方法的上述特点决定了它们经常和synchronized 方法或块一起使用,将它们和操作系统的进程间通信机制作一个比较就会 发现它们的相似性:synchronized方法或块提供了类似于操作系统原语的 功能,它们的执行不会受到多线程机制的干扰,而这一对方法则相当于 block和wakeup原语(这一对方法均声明为synchronized)。它们的结 合使得我们可以实现操作系统上一系列精妙的进程间通信的算法(如信号 量算法),并用于解决各种复杂的线程间通信问题。 suspend0方法和不指定超时期限的wait 0方法的调用都可能产生 死锁。Java并不在语言级别上支持死锁的避免,我们在编程中必须小心地 避免死锁。 五、线程组 线程组是一个Java特有的概念,在Java中,线程组是类ThreadGroup 的对象,每个线程都隶属于唯一一个线程组,这个线程组在线程创建时指 定并在线程的整个生命期内都不能更改。你可以通过调用包含ThreadGroup 类型参数的Thread类构造函数来指定线程属的线程组,若没有指定,则线 程缺省地隶属于名为system的系统线程组。 Java允许我们对一个线程组中的所有线程同时进行操作,比如我们可 以通过调用线程组的相应方法来设置其中所有线程的优先级,也可以启动 或阻塞其中的所有线程。 Java的线程组机制的另一个重要作用是线程安全。线程组机制允许我 们通过分组来区分有不同安全特性的线程,对不同组的线程进行不同的处 理,还可以通过线程组的分层结构来支持不对等安全措施的采用。Java的 ThreadGroup类提供了大量的方法来方便我们对线程组树中的每一个线程 组以及线程组中的每一个线程进行操作。 六、结柬语 多线程的核心在于多个代码块并发执行,本质特点在于各代码块之间 的代码是乱序执行的。我们的程序是否需要多线程,就是要看这是否也是 它的内在特点是否符合多线程的特点。 参考文献: [1]‘面向对象程序设计Java语言>[M],杜春涛,中国铁道出版社, 2007.8. . [2]‘深入学习;Java多线程编程)[岫,美Bil lewisDaniel J Berg,电子工业出版社.2000.12. [3]‘深入浅RiJava多线程程序设计'论文百事通网站,2007.II.
版权声明:本文标题:Java多线程编程的探究 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1710814559a574470.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论