`
yangyiqian
  • 浏览: 117323 次
  • 来自: ...
社区版块
存档分类
最新评论

转贴:Java在多核超线程下多线程编程技术分析

阅读更多
CPU多核时代的到来,相应的也带动了软件设计的需求,以后的程序也许就是多线程、超线程设计方向了。本文主要讲述了在多核时代下,java的超线程技术分析和应用。

一、Java环境下的多线程技术

  构建线程化的应用程序往往会对程序带来重要的性能影响。例如,请考虑这样一个程序,它从磁盘读取大量数据并且在把它们写到屏幕之前处理这些数据 (例如一个DVD播放器)。在一个传统的单线程程序(今天所使用的大多数客户端程序)上,一次只有一个任务执行,每一个这些活动分别作为一个序列的不同阶段发生。只有在一块已定义大小的数据读取完成时才能进行数据处理。因此,能处理数据的程序逻辑直到磁盘读操作完成后才得到执行。这将导致非常差的性能问题。

  在一个多线程程序中,可以分配一个线程来读取数据,让另一个线程来处理数据,而让第三个线程把数据输送到图形卡上去。这三个线程可以并行运行;这样以来,在磁盘读取数据的同时仍然可以处理数据,从而提高了整体程序的性能。许多大量的示例程序都可以被设计来同时做两件事情以进一步提高性能。 Java虚拟机(JVM)本身就是基于此原因广泛使用了多线程技术。

  本文将讨论创建多线程Java代码以及一些进行并行程序设计的最好练习;另外还介绍了对开发者极为有用的一些工具和资源。篇幅所限,不可能全面论述这些问题,所以我想只是重点提一下极重要的地方并提供给你相应的参考信息。

  二、线程化Java代码

  所有的程序都至少使用一个线程。在C/C++和Java中,这是指用对main()的调用而启动的那个线程。另外线程的创建需要若干步骤:创建一个新线程,然后指定给它某种工作。一旦工作做完,该线程将自动被JVM所杀死。

  Java提供两个方法来创建线程并且指定给它们工作。第一种方法是子类化Java的Thread类(在java.lang包中),然后用该线程的工作函数重载run()方法。下面是这种方法的一个示例:
public class SimpleThread extends Thread {
 public SimpleThread(String str) {
super(str);
 }
 public void run() {
for (int i = 0; i < 10; i++) {
 System.out.println(i + " " + getName());
 try {
sleep((long)(Math.random() * 1000));
 } catch (InterruptedException e) {}
}
System.out.println("DONE! " + getName());
 }
}

  这个类子类化Thread并且提供它自己的run()方法。上面代码中的函数运行一个循环来打印传送过来的字符串到屏幕上,然后等待一个随机的时间数目。在循环十次后,该函数打印“DONE!”,然后退出-并由它杀死这个线程。下面是创建线程的主函数:
public class TwoThreadsDemo {
 public static void main (String[] args) {
new SimpleThread("Do it!").start();
new SimpleThread("Definitely not!").start();
 }
}

  注意该代码极为简单:函数开始,给定一个名字(它是该线程将要打印输出的字符串)并且调用start()。然后,start()将调用run()方法。程序的结果如下所示:
0 Do it!
0 Definitely not!
1 Definitely not!
2 Definitely not!
1 Do it!
2 Do it!
3 Do it!
3 Definitely not!
4 Do it!
4 Definitely not!
5 Do it!
5 Definitely not!
6 Do it!
7 Do it!
6 Definitely not!
8 Do it!
7 Definitely not!
8 Definitely not!
9 Do it!
DONE! Do it!
9 Definitely not!
DONE! Definitely not!

  正如你所看到的,这两个线程的输出结果纠合到一起。在一个单线程程序中,所有的“Do it!”命令将一起打印,后面跟着输出“Definitely not!”。

  这个程序的不同运行将产生不同的结果。这种不确定性来源于两个方面:在循环中有一个随机的暂停;更为重要的是,因为线程执行时间没法保证。这是一个关键的原则。JVM将根据它自己的时间表运行这些进程(虚拟机一般支持尽可能快地运行这些线程,但是没法保证何时运行一个给定线程)。对于每个线程可以使一个优先级与之相关联以确保关键线程被JVM处理在次要的线程之前。

  启动一个线程的第二种方法是使用一个实现Runnable接口的类-这个接口也定义在java.lang中。这个Runnable接口指定一个run()方法-然后该方法成为线程的主函数,类似于前面的代码。

  现在,Java程序的一般风格是支持继承的接口。通过使用接口,一个类在后面仍然能够继承(子类化)-如果必要的话(例如,如果该类要在后面作为一个applet使用的话,就会发生这种情况)。

  三、线程的含义

  在采用多线程技术增强性能的同时,它也增加了程序内部运行的复杂性。这种复杂性主要是由线程之间的交互引起的。熟悉这些问题是很重要的,因为随着越来越多的核心芯片加入到Intel处理器中,要使用的线程数目也将相应地增长。如果在创建多线程程序时不能很好地理解这些问题,那么是调试时将很难发现错误。因此,让我们先看一下这些问题及其解决办法。

  等待另一个线程完成:假定我们有一个整型数组要进行处理。我们可以遍历这个数组,每次一个整数并执行相应的操作。或,更高效地,我们可以建立多个线程,这样以来让每个线程处理数组的一部分。假定我们在开始下一步之前必须等待所有的线程结束。为了暂时同步线程之间的活动,这些线程使用了join ()方法-它使得一个线程等待另一个线程的完成。加入的线程(线程B)等待被加入的线程(线程A)的完成。在join()中的一个可选的超时值使得线程B 可以继续处理其它工作-如果线程A在给定的时间帧内还没有终止的话。这个问题将触及到线程的核心复杂性-等待线程的问题。下面我们将讨论这个问题。

  在锁定对象上等待:假定我们编写一个航空公司座位分配系统。在开发这种大型的程序时,为每个连接到该软件的用户分配一个线程是很经常的,如一个线程对应一个机票销售员(在很大的系统中,情况并非总是如此)。如果有两个用户同时想分配同一个座位,就会出现问题。除非采取特殊的措施,否则一个线程将分配该座位而另一个线程将会在做相同的事情。两个用户都会认为他们在这趟航班上拥有一个分配的位子。

  为了避免两个线程同时修改一样的数据项,我们让一个线程在修改数据前锁定数据项。用这种方法,当第二个线程开始作修改时,它将等待到第一个线程释放锁为止。当这种发生时,线程将会看到座位已被分配,而对于座位分配的请求就会失败。两个线程竞争分配座位的问题也就是著名的竞争条件问题,而当竞争发生时有可能导致系统的泄漏。为此,最好的办法就是锁定任何代码-该代码存取一个可由多个线程共同存取的变量。

在Java中存在好几种锁选择。其中最为常用的是使用同步机制。当一个方法的签名包含同步时,在任何给定时间只有一个线程能够执行这个方法。然后,当该方法完成执行时,对该方法的锁定即被解除。例如,
protected synchronized int reserveSeat ( Seat seat_number ){
 if ( seat_number.getReserved() == false ){
seat_number.setReserved();
return ( 0 );
 }
 else return ( -1 );
}

  就是一个方法-在这种方法中每次只运行一个线程。这种锁机制就打破了上面所描述的竞争条件。

  使用同步是处理线程间交互的几种方法中的一种。J2SE 5.0中添加了若干方便的方法来锁定对象。大多数这些方法可以在包java.util.concurrent.locks中找到-一旦你熟悉了Java线程,就应该对它进行详细的研究。

  在锁机制解决了竞争条件的同时,它们也带来了新的复杂性。在这种情况下,最困难的问题就是死锁。假定线程A在等待线程B,并且线程B在等待线程 A,那么这两个线程将永远被锁定-这正是术语死锁的意义。死锁问题可能很难判定,并且必须相当小心以确保在线程之间没有这种依赖性。

  四、使用线程池

  如前所提及,在线程完成执行时,它们将被JVM杀死而分配给它们的内存将被垃圾回收机制所回收。不断地创建和毁灭线程所带来的麻烦是它浪费了时钟周期,因为创建线程确实耗费额外的时间。一个通用的且最好的实现是在程序运行的早期就分配一组线程(称为一个线程池),然后在这些线程可用时再使用它们。通过使用这种方案,在创建时分配给一个线程指定的功能就是呆在线程池中并且等待分配一项工作。然后,当分配的工作完成时,该线程被返回到线程池。

  J2SE 5.0引入了java.util.concurrent包-它包括了一个预先构建的线程池框架-这大大便利了上述方法的实现。有关Java线程池的更多信息及一部教程,请参见http://java.sun.com/developer/JDCTechTips/2004/tt1116.html#2。

  在设计线程程序和线程池时,自然出现关于应该创建多少线程的问题。答案看你怎样计划使用这些线程。如果你基于分离的任务来用线程划分工作,那么线程的数目等于任务的数目。例如,一个字处理器可能使用一个线程用于显示(在几乎所有系统中的主程序线程负责更新用户接口),一个用于标记文档,第三个用于拼写检查,而第四个用于其它后台操作。在这种情况中,创建四个线程是理想的并且它们提供了编写该类软件的一个很自然的方法。

  然而,如果程序-象早些时候所讨论的那个一样-使用多个线程来做类似的工作,那么线程的最佳数目将是系统资源的反映,特别是处理器上可执行管道的数目和处理器的数目的反映。在采用英特尔处理器超线程技术(HT技术)的系统上,当前在每个处理器核心上有两个执行管道。最新的多核心处理器在每个芯片上有两个处理器核心。英特尔指出将来的芯片有可能具有多个核心,大部分是因为额外的核心会带来更高的性能而不会从根本上增加热量或电量的消耗。因此,管道数将会越来越多。

  照上面这些体系结构所作的算术建议,在一个双核心Pentium 4处理器系统上,可以使用四条执行管道并因此可以使用四个线程将会提供理想的性能。在一个双处理器英特尔Xeon?处理器的工作站上,理想的线程数目是 4,因为目前Xeon芯片提供HT技术但是没提供多核心模型。你可以参考下面文档来了解这些新型处理器上的执行管道的数目(http: //www.intel.com/cd/ids/developer/asmo-na/eng/196716.htm)。

  五、小结

  你当在平台上运行线程化的Java程序时,你将可能想要监控在处理器上的加载过程与线程的执行。最好的获得这些数据与管理JVM怎样处理并行处理的JVM之一是BEA的WebLogic JRockit。JRockit还有其它一些由来自于BEA和Intel公司的工程师专门为Intel平台设计和优化的优点。

  不考虑你使用哪一种JVM,Intel的VTune Performance Analyzer将会给你一个关于JVM怎样执行你的代码的很深入的视图-这包括每个线程的性能瓶颈等。另外,Intel还提供了关于如何在Java环境下使用VTune Performance Analyzer的白皮书[PDF 2MB]。

  总之,本文提供了线程在Java平台工作机理的分析。由于Intel还将继续生产HT技术的处理器并且发行更多的多核心芯片,所以想从这些多管道中得到性能效益的压力也会增加。并且,由于核心芯片数目的增加,管道的数目也将相应地增加。唯一的利用它们的优点的办法就是使用多线程技术,如在本文中所讨论的。并且Java多线程程序的优势也越来越明显。
分享到:
评论

相关推荐

    java编程事项(转载收集整理版)

    5. **多线程**:Java提供了内置的多线程支持,通过Thread类和Runnable接口可以创建并管理线程。理解线程同步(如synchronized关键字和Lock接口)以及并发工具类(如ExecutorService和Future)是处理并发问题的关键。...

    转贴:利用钩子技术控制进程创建.txt

    在Windows环境下,钩子通常被用于监控和拦截系统消息、函数调用等行为。例如,可以用来控制进程的创建过程,这对于开发调试工具、安全软件以及系统管理程序非常有用。 ### 控制进程创建的方法 #### 1. API Hooking...

    转贴:随心所欲的Web页面打印技术

    尽管这种方法在某些场景下能提供更精确的打印效果,但也要考虑其对浏览器兼容性、用户安全设置及控件安装等因素的影响。在实际应用中,开发者应根据项目需求和用户环境来决定是否采用这种技术。

    java面试大全(中软国际的大公司)

    2. **集合框架**:Java集合框架是面试中常见的考点,包括List(如ArrayList和LinkedList)、Set(如HashSet和TreeSet)、Map(如HashMap和TreeMap)等接口和类的使用、操作及其性能分析。 3. **内存管理与垃圾回收*...

    flex和java进行CRUD操作(转贴)

    Java则是一种广泛使用的后端编程语言,常用于处理数据存储、业务逻辑以及服务器端功能。本篇文章将围绕"Flex与Java进行CRUD操作"这一主题展开,详细介绍如何使用Flex作为前端,通过与Java后端交互来实现创建(Create)...

    android和java面试大全集

    史上最全的android和java面试文档集。包括有: java程序员面试宝典.txt Java面试宝典2011版-1C,Java基础部分.doc... Java面试题及答案(基础题122道) - 在梦想与现实之间徘徊 - JavaEye技术网站.mht 等等,还有好多。

    易语言源码动网转贴.rar

    5. **多线程处理**:为了提高用户体验,转贴操作可能会在后台线程中执行,这样不会阻塞主线程,保证界面的流畅性。 6. **异常处理**:考虑到网络环境的不稳定性和可能出现的错误,源码中应包含充分的异常处理机制,...

    Java SafeEngine API接口说明

    在使用Java SafeEngine API 开发数字证书应用程序时,开发者需要注意以下几点: * 需要遵守数字证书的相关规定和标准。 * 需要确保数字证书的安全和可靠性。 * 需要遵守相关的法律法规和政策。 六、Java ...

    Struts-menu源码分析(转贴).rar

    通过以上分析,我们可以了解到Struts-menu如何在Struts框架下实现动态菜单功能,同时加深对Struts、MVC模式、自定义标签等概念的理解。实际操作中,阅读和理解源码能帮助我们更好地定制和维护这样的组件,提升开发...

    CString内存泄露HTML.rar

    - **线程安全**:讨论了在多线程环境中使用CString时需要注意的内存管理问题,如数据同步和线程间的资源竞争。 - **调试技巧**:介绍了一些调试MFC程序内存泄露的实用方法,包括使用调试器和专门的内存检测工具。 - ...

    易语言动网转贴

    10. **发送消息**:在窗口编程中,发送消息是进程间通信的一种方式,可以用来通知其他窗口或线程执行某些操作。 11. **桌面窗口**和**窗口句柄**:桌面窗口通常是指用户交互的主窗口,窗口句柄是操作系统用来标识...

    电子政务-导电泡棉转贴装置.zip

    综上所述,"导电泡棉转贴装置"在电子政务中的应用涉及到硬件设计、设备维护、电磁兼容性和法规遵从等多个方面,是保障电子政务系统稳定运行的关键技术之一。通过阅读"行业分类-电子政务-导电泡棉转贴装置.pdf"这份...

    行业资料-电子功用-全自动导电布成型转贴穿管设备及工艺的介绍分析.rar

    标题"行业资料-电子功用-全自动导电布成型转贴穿管设备及工艺的介绍分析.rar"表明这是一份关于电子行业中的特定应用——全自动导电布成型转贴穿管设备及其相关工艺的详细介绍。导电布是一种具有导电性能的材料,常...

    ZZ: 时间管理方法(转贴)

    时间管理是个人和团队高效工作的关键,它涉及到如何规划、组织和优化日常活动,以便在有限的时间内完成更多的任务并实现目标。在这个快速发展的IT行业中,良好的时间管理能力可以帮助程序员提高代码编写效率,项目...

    算法设计与分析 转贴 算法试卷收集 3.doc

    算法设计与分析 算法设计与分析是计算机科学中的一门重要...这些知识点涵盖了算法设计与分析的基本概念和技术,包括算法的时间复杂性、计算模型、语言理论、递归程序、算法设计技术、算法分析技术和算法设计实践等。

Global site tag (gtag.js) - Google Analytics