Thursday, April 29, 2010

什么是线程局部变量(thread-local variable)?轻松使用线程: 不共享有时是最好的

更多精彩请到 http://www.139ya.com


http://ajava.org/course/java/15970.html

什么是线程局部变量(thread-local variable)?轻松使用线程: 不共享有时是最好的

ThreadLocal 类是悄悄地出现在 Java 平台版本 1.2 中的。虽然支持线程局部变量早就是许多线程工具(例如 Posix pthreads 工具)的一部分,但 Java Threads API 的最初设计却没有这项有用的功能。而且,最初的实现也相当低效。由于这些原因, ThreadLocal 极少受到关注,但对简化线程安全并发程序的开发来说,它却是很方便的。在 轻松使用线程的第 3 部分,Java 软件顾问 Brian Goetz 研究了 ThreadLocal 并提供了一些使用技巧。

参加 Brian 的 多线程 Java 编程讨论论坛以获得您工程中的线程和并发问题的帮助。

编写线程安全类是困难的。它不但要求仔细分析在什么条件可以对变量进行读写,而且要求仔细分析其它类能如何使用某个 类。 有时,要在不影响类的功能、易用性或性能的情况下使类成为线程安全的是很困难的。有些类保留从一个方法调用到下一个方法调用的状态信息,要在实践中使这样 的类成为线程安全的是困难的。

管理非线程安全类的使用比试图使类成为线程安全的要更容易些。非线程安全类通常可以安全地在多线程程序中使用,只要 您能确保一个线程所用的类的实例不被其它线程使用。例如,JDBC Connection 类是非线程安全的 — 两个线程不能在小粒度级上安全地共享一个 Connection — 但如果每个线程都有它自己的 Connection ,那么多个线程就可以同时安全地进行数据库操作。

不使用 ThreadLocal 为每个线程维护一个单独的 JDBC 连接(或任何其它对象)当然是可能的;Thread API 给了我们把对象和线程联系起来所需的所有工具。而 ThreadLocal 则使我们能更容易地把线程和它的每线程(per-thread)数据成功地联系起来。

线程局部变量高效地为每个使用它的线程提供单独的线程局部变量值的副本。每个线程只能看到与自己 相联系的值,而不知道别的线程可能正在使用或修改它们自己的副本。一些编译器(例如 Microsoft Visual C++ 编译器或 IBM XL FORTRAN 编译器)用存储类别修饰符(像 staticvolatile )把对线程局部变量的支持集成到了其语言中。Java 编译器对线程局部变量不提供特别的语言支持;相反地,它用 ThreadLocal 类实现这些支持, 核心 Thread 类中有这个类的特别支持。

因为线程局部变量是通过一个类来实现的,而不是作为 Java 语言本身的一部分,所以 Java 语言线程局部变量的使用语法比内建线程局部变量语言的使用语法要笨拙一些。要创建一个线程局部变量,请实例化类 ThreadLocal 的一个对象。 ThreadLocal 类的行为与 java.lang.ref 中的各种 Reference 类的行为很相似; ThreadLocal 类充当存储或检索一个值时的间接句柄。清单 1 显示了 ThreadLocal 接口。


清单 1. ThreadLocal 接口

public class ThreadLocal {
public Object get();
public void set(Object newValue);
public Object initialValue();
}

get() 访问器检索变量的当前线程的值; set() 访问器修改当前线程的值。 initialValue() 方法是可选的,如果线程未使用过某个变量,那么您可以用这个方法来设置这个变量的初始值;它允许延迟初始化。用一个示例实现来说明 ThreadLocal 的工作方式是最好的方法。清单 2 显示了 ThreadLocal 的一个实现方式。它不是一个特别好的实现(虽然它与最初实现非常相似),所以很可能性能不佳,但它清楚地说明了 ThreadLocal 的工作方式。


清单 2. ThreadLocal 的糟糕实现

public class ThreadLocal {
private Map values = Collections.synchronizedMap(new HashMap());
public Object get() {
Thread curThread = Thread.currentThread();
Object o = values.get(curThread);
if (o == null && !values.containsKey(curThread)) {
o = initialValue();
values.put(curThread, o);
}
return o;
}
public void set(Object newValue) {
values.put(Thread.currentThread(), newValue);
}
public Object initialValue() {
return null;
}
}

这个实现的性能不会很好,因为每个 get()set() 操作都需要 values 映射表上的同步,而且如果多个线程同时访问同一个 ThreadLocal ,那么将发生争用。此外,这个实现也是不切实际的,因为用 Thread 对象做 values 映射表中的关键字将导致无法在线程退出后对 Thread 进行垃圾回收,而且也无法对死线程的 ThreadLocal 的特定于线程的值进行垃圾回收。





用 ThreadLocal 实现每线程 Singleton

线程局部变量常被用来描绘有状态“单子”(Singleton) 或线程安全的共享对象,或者是通过把不安全的整个变量封装进 ThreadLocal ,或者是通过把对象的特定于线程的状态封装进 ThreadLocal 。例如,在与数据库有紧密联系的应用程序中,程序的很多方法可能都需要访问数据库。在系统的每个方法中都包含一个 Connection 作为参数是不方便的 — 用“单子”来访问连接可能是一个虽然更粗糙,但却方便得多的技术。然而,多个线程不能安全地共享一个 JDBC Connection 。如清单 3 所示,通过使用“单子”中的 ThreadLocal ,我们就能让我们的程序中的任何类容易地获取每线程 Connection 的一个引用。这样,我们可以认为 ThreadLocal 允许我们创建 每线程单子


清单 3. 把一个 JDBC 连接存储到一个每线程 Singleton 中

public class ConnectionDispenser {
private static class ThreadLocalConnection extends ThreadLocal {
public Object initialValue() {
return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());
}
}
private ThreadLocalConnection conn = new ThreadLocalConnection();
public static Connection getConnection() {
return (Connection) conn.get();
}
}

任何创建的花费比使用的花费相对昂贵些的有状态或非线程安全的对象,例如 JDBC Connection 或正则表达式匹配器,都是可以使用每线程单子(singleton)技术的好地方。当然,在类似这样的地方,您可以使用其它技术,例如用池,来安全地管理 共享访问。然而,从可伸缩性角度看,即使是用池也存在一些潜在缺陷。因为池实现必须使用同步,以维护池数据结构的完整性,如果所有线程使用同一个池,那么 在有很多线程频繁地对池进行访问的系统中,程序性能将因争用而降低。





用 ThreadLocal 简化调试日志纪录

其它适合使用 ThreadLocal 但用池却不能成为很好的替代技术的应用程序包括存储或累积每线程上下文信息以备稍后检索之用这样的应用程序。例如,假设您想创建一个用于管理多线程应用程 序调试信息的工具。您可以用如清单 4 所示的 DebugLogger 类作为线程局部容器来累积调试信息。在一个工作单元的开头,您清空容器,而当一个错误出现时,您查询该容器以检索这个工作单元迄今为止生成的所有调试信 息。


清单 4. 用 ThreadLocal 管理每线程调试日志

public class DebugLogger {
private static class ThreadLocalList extends ThreadLocal {
public Object initialValue() {
return new ArrayList();
}
public List getList() {
return (List) super.get();
}
}
private ThreadLocalList list = new ThreadLocalList();
private static String[] stringArray = new String[0];
public void clear() {
list.getList().clear();
}
public void put(String text) {
list.getList().add(text);
}
public String[] get() {
return list.getList().toArray(stringArray);
}
}

在您的代码中,您可以调用 DebugLogger.put() 来保存您的程序正在做什么的信息,而且,稍后如果有必要(例如发生了一个错误),您能够容易地检索与某个特定线程相关的调试信息。 与简单地把所有信息转储到一个日志文件,然后努力找出哪个日志记录来自哪个线程(还要担心线程争用日志纪录对象)相比,这种技术简便得多,也有效得多。

ThreadLocal 在基于 servlet 的应用程序或工作单元是一个整体请求的任何多线程应用程序服务器中也是很有用的,因为在处理请求的整个过程中将要用到单个线程。您可以通过前面讲述的每线 程单子技术用 ThreadLocal 变量来存储各种每请求(per-request)上下文信息。





ThreadLocal 的线程安全性稍差的堂兄弟,InheritableThreadLocal

ThreadLocal 类有一个亲戚,InheritableThreadLocal,它以相似的方式工作,但适用于种类完全不同的应用程序。创建一个线程时如果保存了所有 InheritableThreadLocal 对象的值,那么这些值也将自动传递给子线程。如果一个子线程调用 InheritableThreadLocalget() ,那么它将与它的父线程看到同一个对象。为保护线程安全性,您应该只对不可变对象(一旦创建,其状态就永远不会被改变的对象)使用 InheritableThreadLocal ,因为对象被多个线程共享。 InheritableThreadLocal 很合适用于把数据从父线程传到子线程,例如用户标识(user id)或事务标识(transaction id),但不能是有状态对象,例如 JDBC Connection






ThreadLocal 的性能

虽然线程局部变量早已赫赫有名并被包括 Posix pthreads 规范在内的很多线程框架支持,但最初的 Java 线程设计中却省略了它,只是在 Java 平台的版本 1.2 中才添加上去。在很多方面, ThreadLocal 仍在发展之中;在版本 1.3 中它被重写,版本 1.4 中又重写了一次,两次都专门是为了性能问题。

在 JDK 1.2 中, ThreadLocal 的实现方式与清单 2 中的方式非常相似,除了用同步 WeakHashMap 代替 HashMap 来存储 values 之外。(以一些额外的性能开销为代价,使用 WeakHashMap 解决了无法对 Thread 对象进行垃圾回收的问题。)不用说, ThreadLocal 的性能是相当差的。

Java 平台版本 1.3 提供的 ThreadLocal 版本已经尽量更好了;它不使用任何同步,从而不存在可伸缩性问题,而且它也不使用弱引用。相反地,人们通过给 Thread 添加一个实例变量(该变量用于保存当前线程的从线程局部变量到它的值的映射的 HashMap )来修改 Thread 类以支持 ThreadLocal 。因为检索或设置一个线程局部变量的过程不涉及对可能被另一个线程读写的数据的读写操作,所以您可以不用任何同步就实现 ThreadLocal.get()set() 。而且,因为每线程值的引用被存储在自已的 Thread 对象中,所以当对 Thread 进行垃圾回收时,也能对该 Thread 的每线程值进行垃圾回收。

不幸的是,即使有了这些改进,Java 1.3 中的 ThreadLocal 的性能仍然出奇地慢。据我的粗略测量,在双处理器 Linux 系统上的 Sun 1.3 JDK 中进行 ThreadLocal.get() 操作,所耗费的时间大约是无争用同步的两倍。性能这么差的原因是 Thread.currentThread() 方法的花费非常大,占了 ThreadLocal.get() 运行时间的三分之二还多。虽然有这些缺点,JDK 1.3 ThreadLocal.get() 仍然比争用同步快得多,所以如果在任何存在严重争用的地方(可能是有非常多的线程,或者同步块被频繁地执行,或者同步块很大), ThreadLocal 可能仍然要高效得多。

在 Java 平台的最新版本,即版本 1.4b2 中, ThreadLocalThread.currentThread() 的性能都有了很大提高。有了这些提高, ThreadLocal 应该比其它技术,如用池,更快。由于它比其它技术更简单,也更不易出错,人们最终将发现它是避免线程间出现不希望的交互的有效途径。






ThreadLocal 的好处

ThreadLocal 能带来很多好处。它常常是把有状态类描绘成线程安全的,或者封装非线程安全类以使它们能够在多线程环境中安全地使用的最容易的方式。使用 ThreadLocal 使我们可以绕过为实现线程安全而对何时需要同步进行判断的复杂过程,而且因为它不需要任何同步,所以也改善了可伸缩性。除简单之外,用 ThreadLocal 存储每线程单子或每线程上下文信息在归档方面还有一个颇有价值好处 — 通过使用 ThreadLocal ,存储在 ThreadLocal 中的对象都是 被线程共享的是清晰的,从而简化了判断 一个类是否线程安全的工作。

Monday, April 19, 2010

Eclipse RCP中多线程Job使用

更多精彩请到 http://www.139ya.com

http://faithful-czx.javaeye.com/blog/378800

Eclipse RCP中多线程Job使用
本文分析了Eclipse中多线程程序的实现,讨论了在Eclipse客户端程序开发中应用多线程的方法和要注意的问题,同时也讨论了多线程程序的一些调试和问题解决的方法。
Eclipse 作为一个开发平台,使用越来越广泛,基于Eclipse Rich Client Platform开发的客户端程序也越来越多。在当今越来越复杂的应用环境中,我们的客户端程序不可避免的要同时进行多任务的处理。一个优异的客户端程序都会允许用户同时启动多个任务,从而大大提高用户的工作效率以及用户体验。本文中我们来谈谈Eclipse中实现多任务的方式。

在我们基于Eclipse的Java程序中,我们有很多种方式提供多任务的实现。熟悉Java的朋友立即会想到Java的Thread类,这是 Java中使用最多的一个实现多任务的类。Eclipse平台为多任务处理提供了自己的API,那就是Job以及UIJob。Eclipse中的Job是对Java Thread的一个封装,为我们实现多任务提供了更方便的接口。以下是Job的基本用法:

清单 1. Job用法示例


Job job = new Job(“Job Name”){
protected IStatus run(IProgressMonitor monitor) {

// 在这里添加你的任务代码
return Status.OK_STATUS;
}
};
job.schedule(1133);//delaytime
job.setUser(true);//if true show UI
job.setPriority(priority)




在Eclipse 中我们也会经常用到Display.asynchExec() 和Display.synchExec()来启动任务的执行。这两个方法主要为了方便我们完成界面操作的任务。以下是 Display.asynchExec()的用法,Display.synchExec()和它类似。


清单 2. Display.synchExec()用法示例
Display.getDefault().asyncExec(new Runnable() {
public void run() {
// 在这里添加你的任务代码
}
});



通常,在Eclipse中我们最好使用Eclipse提供的Job接口来实现多任务,而不是使用Java的thread。为什么呢?主要有以下几个原因:

Job是可重用的工作单元,一个Job我们可以很方便的让它多次执行。 Job提供了方便的接口,使得我们在处理中能够很方便的与外界交流,报告当前的执行进度 Eclipse提供了相应的机制使得程序员可以方便的介入Job的调度,例如我们可以方便的实现每次只有一个同一类型的Job在运行 Eclipse缺省提供了Job管理的程序,可以查看当前所有的Job和它们的进度,也提供UI终止、暂停、继续指定的Job 使 用Job可以提高程序的性能,节省线程创建和销毁的开销。Eclipse中的Job封装了线程池的实现。当我们启动一个Job时,Eclipse不会马上 新建一个Thread,它会在它的线程池中寻找是否有空闲的线程,如果有空闲线程,就会直接用空闲线程运行你的Job。一个Job终止时,它所对应的线程 也不会立即终止,它会被返回到线程池中以备重复利用。这样,我们可以节省创建和销毁线程的开销 下面我们从几个方面来讨论Eclipse中Job的实现和使用方面的问题。

Eclipse中Job的实现

Eclipse 的核心包中提供了一个JobManager类,它实现了IJobManager接口,Eclipse中Job的管理和调度都是由JobManager来实现的。 JobManager维护有一个线程池,用来运行Job。当我们调用Job的schedule方法后,这个Job会被JobManager首先放到一个 Job运行的等待队列中去。之后,JobManager会通知线程池有新的Job加入了运行等待队列。线程池会找出一个空闲的线程来运行Job,如果没有空闲线程,线程池会创建一个新的线程来运行Job。一旦Job运行完毕,运行Job的线程会返回到线程池中以备下次使用。从上面Job运行的过程我们可以看到,JobManager介入了一个Job运行的全过程,它了解Job什么时候开始,什么时候结束,每一时候Job的运行状态。JobManager将这些Job运行的信息以接口的方式提供给用户,同时它也提供了接口让我们可以介入Job的调度等,从而我们拥有了更加强大的控制Job的能力。

为了我们更方便的了解Job所处的状态,JobManager设置Job的一个状态标志位,我们可以通过Job的getState方法获得Job 当前的状态值以了解其状态:

NONE:当一个Job刚构造的时候,Job就会处于这种状态。当一个Job执行完毕(包括被取消)后,Job的状态也会变回这种状态。 WAITING:当我们调用了Job的shedule方法,JobManager会将Job放入等待运行的Job队列,这时Job的状态为 WAITING. RUNNING:当一个Job开始执行,Job的状态会变为RUNNING。 SLEEPING: 当我们调用Job的sleep方法后,Job会变成这一状态。当我们调用schudule方法的时候带上延时的参数,Job的状态也会转入这一状态,在这 一段延时等待的时间中,Job都处于这一状态。这是一种睡眠状态,Job在这种状态中时不能马上转入运行。我们可以调用Job的wakeup方法来将 Job唤醒。这样,Job又会转入WAITING状态等待运行。
Eclipse中的UI线程

另外,在Eclipse的线程处理中,有一个UI线程的概念。Eclipse程序中的主线程是一个特殊的线程,程序启动后会先执行这个线程,也就是我们的 main()函数所在的线程。作为桌面应用程序,我们的主线程主要负责界面的响应以及绘制界面元素,所以通常我们也叫它UI线程。

以下代码,编过SWT应用程序的读者会非常熟悉。它一般出现在main函数的结尾。下面来仔细分析一下它的详细情况。

//当窗口未释放时
while (!shell.isDisposed()) {

//如果display对象事件队列中没有了等待的事件,就让该线程进入等待状态
if (!display.readAndDispatch())
display.sleep();

}




上面的程序实际上就是我们UI线程的处理逻辑:当程序启动后,UI线程会读取事件等待队列,看有没有事件等待处理。如果有,它会进行相应处理,如果没有它会进入睡眠状态。如果有新的事件到来,它又会被唤醒,进行处理。UI线程所需要处理的事件包括用户的鼠标和键盘操作事件,操作系统或程序中发出的绘制事件。一般来说,处理事件的过程也就是响应用户操作的过程。

一个好的桌面应用程序需要对用户的操作作出最快的响应,也就是说我们的UI线程必须尽快的处理各种事件。从我们程序的角度来说,在UI线程中我们不能进行大量的计算或者等待,否则用户操作事件得不到及时的处理。通常,如果有大量的计算或者需要长时间等待(例如进行网络操作或者数据库操作)时,我们必须将这些长时间处理的程序单独开辟出一个线程来执行。这样虽然后台运行着程序,但也不会影响界面上的操作。

除主线程之外的所有线程都是非UI线程。在Eclipse程序中,我们所有对界面元素的操作都必须放到UI线程中来执行,否则会抛出 Exception,所以我们要区分出UI线程和非UI线程,保证我们对UI的操作都在UI线程中执行。

如何判断当前线程是否UI线程: 你可以通过调用Display.getCurrent()来知道当前线程是否是UI线程。如果Display.getCurrent()返回为空,表示当前不是UI线程。


Eclipse中使用线程的几种典型情况

控制Job的并发运行 对于某些Job,为了避免并发性问题,我们希望同时只有一个这样的Job在运行,这时我们需要控制Job的并发运行。在另一种情况下,我们也需要控制 Job 的并发运行:我们在程序中对于一个任务,我们有可能会启动一个Job来执行,对于少量的任务来说,这是可行的,但是如果我们预测可能会同时有大量的任务,如果每一个任务启动一个Job,我们同时启动的Job就会非常多。这些Job会侵占大量的资源,影响其他任务的执行。我们可以使用Job的rule来实现控制Job的并发执行。简单的我们可以通过下面的代码实现。我们先定义一个如下rule:

private ISchedulingRule Schedule_RULE = new ISchedulingRule() {
public boolean contains(ISchedulingRule rule) {
return this.equals(rule);
}
public boolean isConflicting(ISchedulingRule rule) {
return this.equals(rule);
}
};




对于需要避免同时运行的Job,我们可以将它们的rule设成上面定义的rule。如:

myjob1.setRule(Schedule_RULE);
myjob2.setRule(Schedule_RULE);




这样对于myjob1和myjob2这两个Job,它们不会再同时执行。Myjob2会等待myjob1执行完再执行。这是由Eclipse的 JobManager来提供实现的。JobManager可以保证所有启动的Job中,任意两个Job的rule是没有冲突的。我们在上面定义的rule 是最简单的。我们可以重写isConflicting函数来实现一些更加复杂的控制,比如控制同时同类型的Job最多只有指定的个数在运行。但是我们要注意,isConflicting方法不能过于复杂。一旦一个Job的rule与其他Job的rule有冲突,isConflicting方法会调用很多次。如果其中的计算过于复杂,会影响整体的性能。

根据需要执行Job 由于我们有的Job有可能不是立即执行的,在有些情况下,等到该Job准备执行的时候,该Job所要执行的任务已经没有意义了。这时,我们可以使用Job 的 shouldSchedule()和shouldRun()来避免Job的运行。在我们定义一个Job时,我们可以重载shouldSchedule和 shouldRun方法。在这些方法中,我们可以检查Job运行的一些先决条件,如果这些条件不满足,我们就可以返回false。JobManager在安排Job运行时,它会先调用该Job的shouldSchedule方法,如果返回为false,JobManager就不会再安排这个Job运行了。同样,JobManager在真正启动一个线程运行一个Job前,它会调用该Job的shouldRun方法,如果返回false,它不再运行这个 Job。在下面的例子中,我们希望启动一个Job在十秒钟之后更新文本框中的内容。为了保证我们的Job运行时是有意义的,我们需要确保我们要更新的文本框没有被销毁,我们重载了shouldSchedule和shouldRun方法。

Text text = new Text(parent,SWT.NONE);
UIJob refreshJob = new UIJob(“更新界面”){
public IStatus runInUIThread(IProgressMonitor monitor) {

text.setText(“新文本”);
return Status.OK_STATUS;
}
public boolean shouldSchedule(){
return !text.isDisposed();
}
public boolean shouldRun(){
return !text.isDisposed();
}
};
refreshJob.schedule(10000);



在UI线程中涉及长时间处理的任务 我们经常碰到这样一种情况:用户操作菜单或者按钮会触发查询大量数据,数据查询完后更新表格等界面元素。用户点击菜单或者按钮所触发的处理程序一般处于 UI 线程,为了避免阻塞UI,我们必须把数据查询等费时的工作放到单独的Job中执行,一旦数据查询完毕,我们又必须更新界面,这时我们又需要使用UI线程进行处理。下面是处理这种情况的示例代码:

button.addSelectionListener(new SelectionListener(){
public void widgetSelected(SelectionEvent e){
perform();
}

public void widgetDefaultSelected(SelectionEvent e){
perform();
}

private void perform(){
Job job = new Job(“获取数据”){
protected IStatus run(IProgressMonitor monitor){
// 在此添加获取数据的代码
Display.getDefault().asyncExec(new Runnable(){
public void run(){
// 在此添加更新界面的代码
}
});
}
};
job.schedule();
}

});

延时执行Job,避免无用的Job运行 我们经常需要根据选中的对象刷新我们部分的界面元素。如果我们连续很快的改变选择,而每次刷新界面涉及到的区域比较大时,界面会出现闪烁。从用户的角度来说,我们很快的改变选择,希望看到的只是最后选中的结果,中间的界面刷新都是不必要的。

在Jface 中,StructuredViewer提供了addPostSelectionChangedListener方法。如果我们使用这个方法监听 selectionChanged事件,当用户一直按着方向键改变选中时,我们只会收到一个selectionChanged事件。这样我们可以避免过度的刷新界面。

实际上,Jface中就是通过延时执行Job来实现这一功能的。我们也可以自己实现类似功能:

private final static Object UPDATE_UI_JOBFAMILY = new Object();
tableviewer. addSelectionChangedListener (new ISelectionChangedListener (){

public void selectionChanged(SelectionChangedEvent event){
Job.getJobManager().cancel(UPDATE_UI_JOBFAMILY);
new UIJob("更新界面") {
protected IStatus runInUIThread (IProgressMonitor monitor) {
//更新界面
return Status.OK_STATUS;
}

public boolean belongsTo(Object family){
return family== UPDATE_UI_JOBFAMILY;
}

}.schedule(500);
}
});



首先,我们需要将界面更新的代码放到一个UIJob中,同时我们将Job延时500毫秒执行(我们可以根据需要改变延时的时间)。如果下一个 selectionChanged事件很快到来,我们的调用Job.getJobManager().cancel (UPDATE_UI_JOBFAMILY)将以前未运行的Job取消,这样只有最后一个Job会真正运行。

在UI线程中等待非UI线程的结束 有时,我们在UI线程中需要等待一个非UI线程执行完,我们才能继续执行。例如,我们在UI线程中要显示某些数据,但是这些数据又需要从数据库或者远程网络获取。于是,我们会启动一个非UI的线程去获取数据。而我们的UI线程必须要等待这个非UI线程执行完成,我们才能继续执行。当然,一种简单的实现方法是使用join。我们可以在UI线程中调用非UI线程的join方法,这样我们就可以等待它执行完了,我们再继续。但是,这会有一个问题。当我们的UI线程等待时,意味着我们的程序不会再响应界面操作,也不会刷新。这样,用户会觉得我们的程序象死了一样没有反应。这时,我们可以使用 ModalContext 类。你可以将你要执行的获取数据的任务用ModalContext的run方法来运行(如下)。ModalContext会将你的任务放到一个独立的非 UI线程中执行,并且等待它执行完再继续执行。与join方法不同的是,ModalContext在等待时不会停止UI事件的处理。这样我们的程序就不会没有响应了。

try {
ModalContext.run(new IRunnableWithProgress(){
public void run(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
/*需要在非UI线程中执行的代码*/
ModalContext.checkCanceled(monitor);
}
}, true, new NullProgressMonitor(), Display.getCurrent());
} catch (InvocationTargetException e) {

} catch (InterruptedException e) {
}



针对相关联的Job统一进行处理 有时,我们需要对相关联的Job一起处理。例如需要同时取消这些Job,或者等待所有这些Job结束。这时我们可以使用Job Family。对于相关联的Job,我们可以将它们设置成同一个Job Family。我们需要重载Job的belongsTo方法以设置一个Job的Job Family。

Private Object MY_JOB_FAMILY = new Object();
Job job = new Job(“Job Name”){
protected IStatus run(IProgressMonitor monitor) {
// 在这里添加你的任务代码
return Status.OK_STATUS;
}

public boolean belongsTo(Object family){
return MY_JOB_FAMILY.equals(family);
}
};



我们可以使用JobManager的一系列方法针对Job Family进行操作:

Job.getJobManager().cancel(MY_JOB_FAMILY); //取消所有属于MY_JOB_FAMILY的所有Job
Job.getJobManager().join(MY_JOB_FAMILY); //等待属于MY_JOB_FAMILY的所有Job结束
Job.getJobManager().sleep(MY_JOB_FAMILY); //将所有属于MY_JOB_FAMILY的Job转入睡眠状态
Job.getJobManager().wakeup(MY_JOB_FAMILY); //将所有属于MY_JOB_FAMILY的Job唤醒




线程死锁的调试和解决技巧

一旦我们使用了线程,我们的程序中就有可能有死锁的发生。一旦发生死锁,我们发生死锁的线程会没有响应,导致我们程序性能下降。如果我们的UI线程发生了死锁,我们的程序会没有响应,必须要重启程序。所以在我们多线程程序开发中,发现死锁的情况,解决死锁问题对提高我们程序的稳定性和性能极为重要。

如果我们发现程序运行异常(例如程序没有响应),我们首先要确定是否发生了死锁。通过下面这些步骤,我们可以确定是否死锁以及死锁的线程:

在Eclipse中以Debug模式运行程序 执行响应的测试用例重现问题 在Eclipse的Debug View中选中主线程(Thread[main]),选择菜单Run->Suspend。这时Eclipse会展开主线程的函数调用栈,我们就可以看到当前主线程正在执行的操作。 通常,Eclipse在等待用户的操作,它的函数调用栈会和以下类似:


图片示例


如果主线程发生死锁,函数调用栈的最上层一般会是你自己的函数调用,你可以查看一下你当前的函数调用以确定主线程在等待什么 使用同样的方法查看其他线程,特别是那些等待UI线程的线程 我们需要找出当前线程相互的等待关系,以便找出死锁的原因。我们找出死锁的线程后就可以针对不同情况进行处理:

减小锁的粒度,增加并发性 调整资源请求的次序 将需要等待资源的任务放到独立的线程中执行
Job使用中要注意的问题

不 要在Job中使用Thread.sleep方法。如果你想要让Job进入睡眠状态,最好用Job的sleep方法。虽然,使用Thread.sleep和 Job的sleep方法达到的效果差不多,但是它们实现的方式完全不同,对系统的影响也不一样。我们知道Eclipse中Job是由Eclipse的 JobManager来管理的。如果我们调用Job的sleep方法,JobManager会将Job转入睡眠状态,与其对应的线程也会重新放入线程池等 待运行其他Job。而如果我们在Job中直接调用Thread.sleep方法,它会直接使运行Job的线程进入睡眠状态,其他Job就不可能重用这个线 程了。同时,虽然运行该Job的线程进入了睡眠状态,Job的状态还是Running(运行状态),我们也不能用Job的wakeup方法唤醒该Job了 Job 的取消。一般我们会直观的认为,一旦调用Job的cancel方法,Job就会停止运行。实际上,这并不一定正确,当Job处于不同的状态时,我们调用 Job的cancel方法所起的效果是不同的。当Job在WAITING状态和SLEEPING状态时,一旦我们调用cancel方法, JobManager会将Job直接从等待运行的队列中删除,Job不会再运行了,这时cancel方法会返回true。但是如果Job正在运行, cancel方法调用并不会立即终止Job的运行,它只会设定一个标志,指明这个Job已经被取消了。我们可以使用Job的run方法传入的参数 IProgressMonitor monitor,这个参数的isCanceled方法会返回Job是否被取消的状态。如果需要,我们必须在我们的代码的适当位置检查Job是否被取消的标 志,作出适当的响应。另外,由于调用Job的cancel方法不一定立即终止Job,如果我们需要等待被取消的Job运行完再执行,我们可以用如下代码: if (!job.cancel())
job.join();


Join方法的使用。由于join方法会导致一个线程等待另一个线程,一旦等待线程中拥有一个被等待线程所需要的锁,就会产生死锁。当我们的线程中需要用到同步时,这种死锁的情况非常容易出现,所以我们使用join时必须非常小心,尽量以其他方法替代。 避 免过时的Job造成的错误。由于我们启动的线程并不一定是马上执行的,当我们的Job开始运行时,情况可能发生了变化。我们在Job的处理代码中要考虑到 这些情况。一种典型的情况是,我们在启动一个对话框或者初始化一个ViewPart时,我们会启动一些 Job去完成一些数据读取的工作,一旦数据读取结束,我们会启动新的UI Job更新相应的UI。有时,用户在打开对话框或者View后,马上关闭了该对话框或者View。这时我们启动的线程并没有被中断,一旦在Job中再去更 新UI,就会出错。在我们的代码中必须作相应的处理。所以,我们在线程中更新界面元素之前,我们必须先检查相应的控件是否已经被dispose了 结束语

在我们进行基于Eclipse的客户端开发时,使用多线程可以大大的提供我们的程序并发处理能力,同时对于提高用户体验也有很好的帮助。但是,多线程程序也有其不利的一面,我们也不要滥用线程:

首先,多线程程序会大大的提高我们程序的复杂度,使得我们的开发和调试更加困难 其次,过多的线程容易引发死锁、数据同步等并发问题的发生 另外,由于线程创建和销毁需要开销,程序的整体性能可能因为过多线程的使用而下降 所以,我们在使用线程时一定要谨慎。本文对Eclipse线程的讨论,希望能对大家使用线程有所帮助。由于实际情况较为复杂,文中所提到的方法仅供参考,读者对于不同的实际问题需要进行具体分析,从而找出最佳的解决方案。

Display与Shell

更多精彩请到 http://www.139ya.com

http://hoodman.javaeye.com/blog/497262

一个SWT程序至少需要一个Display对象,创建Display的线程称为UI线程,一个线程中不能有两个Display。
第一次调用Display.getDefault()会创建一个Display,以后再次调用会返回创建的Display。
或者显式定义一个Display:Display dis=new Display(),以后调用getdefault()也会返回创建的Display。

多线程程序中,采用Display.getCurrent()可以获取当前线程的Display,调用 Display.findDisplay(Thread)可以找到任意线程的Display。
在多线程中不要使用getDefault(),容易导致非法线程访问异常。

Shell代表一个窗口,可以基于Display创建Shell,或者基于父Shell创建子shell,若父shell被关闭,子Shell也自动关闭。

使用Display.getMonitors()可以获取与Display相关的所有监视器,getPrimaryMonitor()可以获取主监视器,监视器(Monitor)的边界代表屏幕大小,客户区通常小于监视器尺寸.Monitor.getBounds();monitor.getClientArea(),用于获取边界和客户区。

多线程同步:Display维护一个自定义的事件队列,供后台线程与UI线程同步,后台线程利用Runalble对象插入事件队列,display执行消息循环时就会执行这些操作,Display提供了两个方法向队列中插入事件:
Display.syncExec(),Display.asyncExec()。前者同步调用,通知UI在下一个事件循环时执行Runalble的 run方法,同时线程将被阻塞,直到runable执行完毕,后者为异步调用,区别是线程不会被阻塞,runable执行完毕后不会得到通知。
public static void main(String[] args) {
final Display display = new Display();
Shell shell = new Shell(display);
shell.setText("Hello world!");
final Button button = new Button(shell, SWT.NONE);
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
Thread thread = new Thread() {
@Override
public void run() {
try {
Thread.sleep(10000);//代表大量运算
} catch (Exception e2) {
// TODO: handle exception
}
display.syncExec(new Runnable() {
@Override
public void run() {
button.setText("finish");//运算完毕,提示通知
}
});
}
};
thread.start();
}
});
button.setText("button");
button.setBounds(20, 15, 155, 25);

shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}

最近的Eclipse plugin开发总结

更多精彩请到 http://www.139ya.com

http://macrochen.javaeye.com/blog/143244

List控件没有提供addDblClickListener方法,如果要处理双击事件,则需要这样处理:


java 代码

1. list.addListener(SWT.MouseDoubleClick, new Listener() {
2. public void handleEvent(Event event) {
3. System.out.println(event);
4. }});

在WizardPage中要实现想到的前进和后退需要这么使用
((WizardDialog)getWizard().getContainer()).showPage(getNextPage());
而不能简单的通过wizardPage的setVisibile()来设置可见和不可见

ed.getCommandStack().execute(command);和 command.execute()是有区别的,前者会告诉editor当前的EditorInput已经发生改变,而后者不会

在保存emf模型的时候,遍历每一个emf节点,都会执行 org.eclipse.emf.ecore.xmi.impl.XMLSaveImpl.saveElementID该方法
保存属性执行 org.eclipse.emf.ecore.xmi.impl.XMLSaveImpl.saveDataTypeSingle(EObject o, EStructuralFeature f)该方法

如何定义扩展点
我们可以给插件定义扩展来增加插件的灵活性,满足用户的定制要求.
每一个扩展点都必须包括一个唯一标识符用来标识当前定义的扩展点,在使用扩展点的时候的扩展点id就是插件全限定+这个唯一标识符,而且每一个扩展点都会关联一个schema文件,该文件用来告诉用户如何使用该扩展点,不过该schema文件不是必须的,但是他可以为我们定义的扩展点进行验证并自动生成相关的文档信息,schema文件以.exsd的格式存放在插件所在目录的schema文件夹下
图片文件之类的,在定义扩展点属性的时候,必须将其kind属性设置为resource,而如果是java类的话,则需要将kind属性设置为java

Display.syncExec() and asyncExec()方法的区别,前者是同步执行,这里的"步"可以理解是管道,也就是在一个管道中挨个执行线程(当前线程+指定线程),后者是异步执行,也就是它可以另起一个管道来执行指定的线程.syncExec在指定的线程执行完毕之后才返回,asyncExec无论指定的线程是否结束都返回到当前的线程

获得IWorkBenchWindow的方法:
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();

得到pluginId要这么用:
CommonPlugin.getDefault().getBundle().getSymbolicName()

根据pluginId得到一个bundle
Bundle bundle = Platform.getBundle(pluginId);

通过调用Display.getCurrent()来知道当前线程是否是UI线程。如果 Display.getCurrent()返回为空,表示当前不是UI线程。只有一个UI线程,其他的都是非UI线程

Job的join方法表示当前job执行完成之后再执行job后面的代码,否则这个顺序是无序的

在非UI线程中调用UI线程会抛出SWTException异常,要解决这个问题需要使用 Display.syncExec()或者asyncExec()方法,还有一种解决方法就是用时UIJob,不过必须复写 runInUIThread()方法,将逻辑代码放在该方法中执行而不是run()方法中

打开对话框查找多个制定类型的文件可以参考 org.eclipse.jdt.internal.ui.wizards.buildpaths.LibrariesWorkbookPage.openExtJarFileDialog() 方法

java.lang.VerifyError: (xxx) Wrong return type in function问题的解决
一般出现这种错误是由于java complier和jre的版本不一致导致的,一般在升级的时候很容易碰到,比如有时候指定在java 1.4下编译,但是实际上我们的程序在java5.0下跑
还有一种可能就是升级之后,原来的类已经从一个包里面移动到另外一个包下面了,而加载classpath的时候存在两份

跟踪键盘事件的时候, 在该方法下加断点void org.eclipse.swt.widgets.TypedListener.handleEvent(Event e)

对于显示tooltip提示信息需要在void org.eclipse.swt.widgets.TypedListener.handleEvent(Event e)中监听SWT.MouseHover类型的事件

aptana js editor中的提示信息的获得通过 com.aptana.ide.editors.unified.hover.LexemeTextHover.getHoverInfo(ITextViewer textViewer, IRegion hoverRegion)
取得

在aptana中对文档的分割使用到Lexme的概念, 而所有的提示, 自动提示功能都是建立在Lexme的基础上的

aptana加载内容提示配置文件在 com.aptana.ide.editor.js.JSLanguageEnvironment中实现

向日志中添加一条出错信息:
IStatus status = new Status(IStatus.ERROR,
"org.eclipse.jface", //$NON-NLS-1$
0, message, new Exception());
Policy.getLog().log(status);

IWorkspace ws = ResourcesPlugin.getWorkspace();用来取得workspace对象