深入 Eclipse 多线程机制

简介

Eclipse 提供了一套多线程类库(包括 Job 等)极大的方便了开发人员对多线程程序的处理。本文通过对 Eclipse 内核代码的研究,分析 Eclipse 多线程库的内部实现机制,特别是其内部线程池的实现方式,Job 的调度,线程同步机制等。读者通过阅读本文,可以深入了解 Eclipse 多线程机制,同时加深对 Java 线程的理解。这对于我们设计多任务系统,以及解决多线程问题将大有帮助。建议读者在阅读本文前,先阅读本文作者的另一篇文章“ Eclipse 客户端程序中多线程的使用”( http://www.ibm.com/developerworks/cn/opensource/os-cn-eclipse-multithrd/)对您理解本文大有裨益。

文章的组织结构如下:

  • Jobs 的框架
  • 并发模型-线程池机制
  • 并发模型- Job 的调度
  • 并发模型-线程同步机制
  • 小结

Jobs 的框架

Eclipse 在 org.eclipse.core.runtime.osgi 运行时插件里提供了 Jobs API 。 Jobs API 被广泛的应用到 Eclipse 平台中,用户所开发的 eclipse 插件里。 Job 是 Eclipse 运行时重要的组成部分(基于 equinox 的 OSGi 框架则是 Eclipse 运行时的最重要的组成部分)。 Job 可以理解成被平台调用异步运行的代码块。 Job 可以异步的执行,多个 Jobs 可以并发执行。那么读者会问了?为什么 eclipse 平台要提供这样的 API 出来,为什么不直接使用 java.lang.Thread 呢?

原因有以下几点:

1)性能更好:通过使用线程池实现了线程共享,减少了创建和销毁线程的开销,提高了性能。

2)交互响应信息:Job 提供了一个框架,开发人员使用 Job 很容易实现与用户的交互,例如允许用户取消 Job 的执行或者显示 Job 。

3)调度的灵活性:可以马上运行一个 Job,可以稍后运行一个 Job, 还可以反复的运行一个 Job

4)Job 的监听机制:Job 监听器监听 Job 的状态信息,比如,知道一个 Job 何时开始运行以及何时结束运行等。

5)优先级及互斥的灵活应用:Job 提供了多种方式来控制 Job 的调度,开发者可以设定 Job 的优先级(读者应注意这一点,JobManager 不保证优先级高的 Job 一定比优先级低的 Job 先被调度执行),也可以使用调度规则保证 Jobs 的同步与互斥。

下面我们首先介绍一下 Jobs 的框架 , 如图 1 所示。其囊括了 org.eclipse.core.runtime.jobs 包内的所有接口和类。


图 1. Jobs 框架
Jobs 框架

IJobManager 是 Job 管理器类的接口,其中定义了一些对 Job 以及 JobFamily 操作的一些 API 。有关 Job 管理器的实现,我们将在线程池机制一节中作详细介绍。

当并发执行多个 Jobs 的时候,可能会引发冲突(Conflict)。 Job 的框架则充分考虑到了这种情况,并提供了管理和避免冲突的工具。 ISchedulingRule 接口,是用来管理共享资源访问冲突的技术。它使得 IJobManager 能够识别出冲突的 Jobs,进而能保证这些不能在一起执行的 Jobs 不在同一时间被调度或者启动。 ISchedulingRule 接口的子类 MultiRule 表示一组固定的子调度规则,如果任何一个 MultiRule 的子调度规则和另一个调度规则相冲突,那么该 MultiRule 和另一个调度规则就会发生冲突。形式化的说,一个组合调度规则表示其所有的子调度规则关于 isConflicting 方法等价的逻辑交集。组合调度规则不会再包含另一个组合调度规则,如果你把一个组合规则作为子规则加入到另一个组合规则中,算法就是该组合规则的所有子规则被加入到另一个组合规则中去了。

ILock ,锁是用来对排他性资源的访问控制的。锁是可以重入的,也就是说同一个线程在不释放已有锁的情况下,可多次获取该锁。当成功获取锁的次数和成功释放锁的次数相等时,锁才能被释放掉。通过实现释放等待策略,锁避免了循环等待的死锁。如果一组线程陷入一个死锁,一个线程将会失去它所拥有的锁,进而打破死锁,使得其它的线程能够继续执行。一旦线程获得了运行所需要的所有的锁,它将获得对锁控制的排他性资源的访问。

当一个线程正等待一个 acquire() 方法调用的时候,才可能会失去锁 ( 参见本文的线程同步机制部分 ) 。程序应用队列(先来先得)管理不同的线程来获取锁。线程获取的锁一定要释放掉,一般在 finally 程序块内释放锁。例如:

lock.acquire(); 
 try { 
     // ... 执行程序 ... 
 } finally { 
     lock.release(); 
 }

IJobChangeListener 接口,监听到 Job 的状态信息,进而执行相应的逻辑操作。

ProgressProvider 类,为正在运行的 jobs 向 Job 管理器提供进度控制器。任何时候,它只有一个实例存在。该类仅由平台相关的插件内部使用。

下面我们进入到 org.eclipse.core.internal.jobs 包内继续对 eclipse 并发 API 的进行讲解。该包内的类是 org.eclipse.core.runtime.jobs 包内接口或抽象类的具体实现。


并发模型-线程池机制

为了更高效的运行 Job,Eclipse 实现了一套线程池。 Eclipse 的线程池的实现主要涉及到三个类:Worker,WorkPool 和 JobManager 。


图 2. Worker 和 WorkPool
Worker 和 WorkPool

Worker 类继承自 java.lang.Thread 类,它执行 WorkPool 所提供的 job 。

WorkPool 顾名思义,维护了一个 worker 线程的池子(也就是线程的集合)。线程是根据需要延迟构造的,如果过一会不用的话,最终线程会被丢弃掉。实际上来讲,线程池为 JobManager 提供了创建和销毁线程的相应管理。 JobManager 是 eclipse 平台添加的用以管理 Jobs 的 API 。

JobManager 对 job 和锁 (lock) 提供调度(schedule),检索 (query) 和维护 (maintain) 的相关支持。 Job 管理器主要提供如下的服务:

1)维持一个等待运行的 Job 队列,可以使用 schedule 方法把一个 job 加入到该等待队列中。

2)允许对 Job family 的操作。(Job family 就是一组相关的 job 集合)可以取消,休眠或自动的唤醒某个 job family, Job 管理器也提供对于给定的 job family 检索其中 job 的服务。

3)允许监听器知晓当前运行的 job 的进度信息。当运行的 job 的状态信息发生改变时,监听器能监听到,进而做出相应的逻辑操作。

4)提供一个用来创建锁对象的工厂。 Lock 对象有预防死锁的策略,可以看成是一个灵敏的监视器(Monitor)

5)为正在等待某一运行的 job 或 job family 完成的客户提供反馈信息。

下面我们用顺序图图 3 来说明,当一个 Job 调用 schedule 方法时,内部的实现逻辑是怎么样的。 Job 继承了 InternalJob(Job 的内部实现类),Job 的 schedule 方法直接调用了父类 InternalJob 的 schedule 方法。 InternalJob 则持有 JobManager 的实例,在其 schedule 方法里调用了 JobManager 的 schedule 方法。清单 1 给出了 JobManager 的 schedule 方法,读者可见,在方法的末尾处,JobManager 调用了线程池类的 jobQueued 方法,。线程池类的 jobQueued 方法是线程管理的关键方法,它首先检查是否有可用的 Worker(正在睡眠的线程),如果有它通过调用 notify()来唤醒 Worker 开始执行 Job,如果没有可用的 Worker,它会创建一个新的 Worker(线程),并启动这个 Worker 执行。清单 2 给出了 jobQueued 方法的实现。这里需要注意的是 Job.schedule()方法只是启动了一次 Job 的调度,它有可能触发创建一个新的 Worker,但是新创建的 Worker 并不一定会执行刚 schedule 的 Job,具体哪个 Job 会被执行是由调度算法决定的。我们稍后会讨论 Job 的调度。


图 3. schedule 方法调用顺序图
schedule 方法调用顺序图

清单 1. JobManager 的 schedule 方法
protected void schedule(InternalJob job, long delay) { 
	 if (!active) 
		 throw new IllegalStateException("Job manager has been shut down."); 
	 Assert.isNotNull(job, "Job is null"); //$NON-NLS-1$ 
	 //call hook method outside sync block to avoid deadlock 
	 if (!job.shouldSchedule()) 
		 return; 
	 synchronized (lock) { 
		 //can't schedule a job that is already waiting, sleeping, or running 
		 if (job.getState() != Job.NONE) 
			 return; 
		 //if it's a decoration job, don't run it right now if the system is busy 
		 if (job.getPriority() == Job.DECORATE) { 
			 long minDelay = running.size() * 100; 
			 delay = Math.max(delay, minDelay); 
		 } 
		 if (delay > 0) { 
			 job.setStartTime(System.currentTimeMillis() + delay);
			 changeState(job, Job.SLEEPING); 
		 } else { 
	 job.setStartTime(System.currentTimeMillis() + delayFor(job.getPriority()));
	 changeState(job, Job.WAITING); 
		 } 
	 } 
	 //notify listeners outside sync block 
	 jobListeners.scheduled((Job) job, delay); 
	 //call the pool outside sync block to avoid deadlock 
	 pool.jobQueued(job); 
 }


清单 2. WorkPool 的 jobQueued 方法
protected synchronized void jobQueued(InternalJob job) { 
	 //if there is a sleeping thread, wake it up 
	 if (sleepingThreads > 0) { 
		 if (JobManager.DEBUG) 
			 JobManager.debug("notifiying a worker"); //$NON-NLS-1$
			 notify();
			 return; 
	 } 
	 int threadCount = numThreads; 
	 //create a thread if all threads are busy and we're under the max size 
	 //if the job is high priority, we start a thread no matter what 
	 if (busyThreads >= threadCount &&
	  (threadCount < MAX_THREADS || 
	  (job != null && job.getPriority() == Job.INTERACTIVE))) { 
		 Worker worker = new Worker(this); 
		 add(worker); 
		 if (JobManager.DEBUG) 
			 JobManager.debug("worker added to pool: " + worker); 
			 //$NON-NLS-1$
			 worker.start();
			 return; 
	 } else if (threadCount > MAX_THREADS) { 
	  String msg = "The job manager has stopped allocating worker threads 
	   because too many background tasks are running."; //$NON-NLS-1$ 
      InternalPlatform.getDefault().
       log(new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, 1, msg, null)); 
	 } 
 }


清单 3. Worker 的 run 方法
public void run() { 
	 setPriority(Thread.NORM_PRIORITY); 
	 try { 
		 while ((currentJob = pool.startJob(this)) != null) { 
			 //if job is null we've been shutdown 
			 if (currentJob == null) return; 
			 currentJob.setThread(this); 
			 IStatus result = Status.OK_STATUS; 
			 try { 
				 result = currentJob.run(currentJob.getMonitor()); 
			 } catch (OperationCanceledException e) { 
				 result = Status.CANCEL_STATUS; 
			 } catch (Exception e) { 
				 result = handleException(currentJob, e); 
			 } catch (LinkageError e) { 
				 result = handleException(currentJob, e); 
		 } finally { 
				 //clear interrupted state for this thread 
				 Thread.interrupted(); 
				 pool.endJob(currentJob, result); 
		 if ((result.getSeverity() & (IStatus.ERROR | IStatus.WARNING)) != 0) 
					 log(result); 
			  currentJob = null; 
			 } 
		 } 
	 } catch (Throwable t) { 
		 t.printStackTrace(); 
	 } finally { 
		 currentJob = null; 
		 pool.endWorker(this); 
	 } 
 }

Worker 的 run 方法中的主体是一个循环,它循环的调用线程池的 startJob 方法去获取要执行的 Job,然后执行得到的 Job 。在清单 4 中,我们给出了线程池 startJob 方法的实现。在 worker 的线程中当前的 job 执行完后,worker 调用 WorkPool 的 startJob 方法获取下一个用以在该线程里执行的 job 。如果立即能获得一个 job 则返回执行,否则,该 worker 线程休眠给定的时间,然后再试图获取一个可执行的 job,如果仍然没有可执行的 job,且时间过期了,则调用 WorkPool 的 endWorker 从池中把此 worker 删除,并返回 null 值,那么该 worker 的线程就被杀掉了。(研读 Eclipse 平台代码时,我们发现代码中的重要的方法,复杂算法都有较详尽的注释说明,易于理解,本文亦不必逐句的讲解了。)


清单 4. WorkPool 的 startJob 方法
protected InternalJob startJob(Worker worker) { 
 //if we're above capacity, kill the thread 
  synchronized (this) { 
	if (!manager.isActive() || numThreads > MAX_THREADS) { 
	//must remove the worker immediately to prevent all threads from expiring 
			 endWorker(worker); 
			 return null; 
		 } 
	 } 
	 Job job =manager.startJob();
	 // 重 JobManager 获取下一个可执行的 job
	 //spin until a job is found or until we have been idle for too long 
	 long idleStart = System.currentTimeMillis(); 
	 while (manager.isActive() && job == null) { 
		 long hint = manager.sleepHint();
		 if (hint > 0) sleep(Math.min(hint, BEST_BEFORE)); 
		 // 当前 worker 线程休眠 
		 job = manager.startJob();
//if we were already idle, and there are still no new jobs, then the thread can expire 
		 synchronized (this) { 
			 if (job == null && 
			 (System.currentTimeMillis() - idleStart > BEST_BEFORE) && 
			 (numThreads - busyThreads) > MIN_THREADS) { 
 //must remove the worker immediately to prevent all threads from expiring 
				 endWorker(worker); 
				 return null; 
			 } 
		 } 
	 } 
if (job != null) { 
	 incrementBusyThreads(); 
	 //if this job has a rule, then we are essentially acquiring a lock 
	 if ((job.getRule() != null) && !(job instanceof ImplicitJobs.ThreadJob)) { 
	 //don't need to reaquire locks because it was not recorded in the graph 
	 //that this thread waited to get this rule 
	 manager.getLockManager().addLockThread(Thread.currentThread(), job.getRule()); 
	 } 
	 //see if we need to wake another worker 
   if (manager.sleepHint() <= 0) jobQueued(null); 
	 } 
	 return job; 
 }

线程池内还提供了 shutdown,endWorker 和 endJob 等方法,用以结束线程的执行,甚至在 OutOfMemoryError 发生时,endJob 方法仍可被调用执行。关于线程池对销毁线程的相应管理,感兴趣的读者可去研读代码,本文略之。

本小节主要以 Job 的 schedule 方法调用为主线介绍了 Jobs API 的线程池机制。倘若读者有基于 Java Thread 构建自己的线程服务 API 的话,线程池技术不可或缺。


并发模型- Job 的调度

当原 XML 文件中的信息被解析进来,并存储在已经建立的数据模型里,此时的数据模型称为从上几节的介绍中我们知道,Job 被 schedule 以后并不一定马上运行,至于一个 Job 什么时候能够运行,这是由 Job 调度算法决定的。

Eclipse Job API 提供了 JobQueue 类,一个基于连接表的优先级队列。该类提供了诸如入队,出队,删除,判空等队列常见的方法。值得一提的是,JobQueue 类的构造器方法有一布尔类型的参数 allowConflictOvertaking,如果该值是 false,那么在队列中,优先级高的 job 只能跃迁到和它没有冲突的,且优先级低的 jobs 的前面。如果变量值为 true,那么允许优先级高的 jobs 跃迁到低优先级的 jobs 的前面,而不必考虑 Jobs 之间是否有冲突。

JobManager 维护了两个基于 JobQueue 的队列,一个是等待(waiting)队列,等待运行的 Jobs 队列,另一个是休眠(sleeping)队列。 清单 5 给出了,基于单例(singleton)模式构建 JobManager 实例的代码。在构造器内,waiting 队列的 allowConflictOvertaking 值是 false, 而在 sleeping 队列中,该值是 true 。从清单 5 中,我们还知道运行的 jobs 是放在 HashSet 中的。


清单 5. 创建 JobManger 实例
public static synchronized JobManager getInstance() { 
	 if (instance == null) 
		 new JobManager().startup(); 
	 return instance; 
 } 
 private JobManager() { 
	 instance = this; 
	 synchronized (lock) { 
		 waiting = new JobQueue(false); 
		 sleeping = new JobQueue(true); 
		 running = new HashSet(10); 
		 pool = new WorkerPool(this); 
	 } 
 }

在 sleeping 和 waiting 两个变量前,有注释说明,这两个变量只能在方法 changeState 中被改变。清单 6 给出了 changeState 方法的实现。我们在上节清单 2 也注意到,当调用 schedule 方法时候(在线程池调用 jobQueued 方法之前),如果没有延迟,则调用 changeState (job, Job.WAITING); 如果有时间延迟(delay!= 0), 则调用 changeState(job, Job.SLEEPING); 用以设置 job 的状态信息。


清单 6. changeState 方法
private void changeState(InternalJob job, int newState) { 
  synchronized (lock) { 
	int oldState = job.internalGetState(); 
	switch (oldState) { 
	 case Job.NONE : 
			 break; 
			 case InternalJob.BLOCKED : 
				 //remove this job from the linked list of blocked jobs 
				 job.remove(); 
				 break; 
			 case Job.WAITING : 
				 try { 
					 waiting.remove(job); 
				 } catch (RuntimeException e) { 
	Assert.isLegal(false, "Tried to remove a job that wasn't in the queue");  
				 } 
				 break; 
			 case Job.SLEEPING : 
				 try { 
					 sleeping.remove(job); 
				 } catch (RuntimeException e) { 
	Assert.isLegal(false, "Tried to remove a job that wasn't in the queue");  
				 } 
				 break; 
			 case Job.RUNNING : 
				 running.remove(job); 
				 break; 
			 default : 
	Assert.isLegal(false, "Invalid job state: " + job + ", state: " + oldState);  
		 } 
		 job.internalSetState(newState); 
		 switch (newState) { 
			 case Job.NONE : 
			 case InternalJob.BLOCKED : 
				 break; 
			 case Job.WAITING :waiting.enqueue(job);break; 
			 case Job.SLEEPING :sleeping.enqueue(job);break; 
			 case Job.RUNNING : 
				 running.add(job); 
				 break; 
			 default : 
	Assert.isLegal(false, "Invalid job state: " + job + ", state: " + newState);  
		 } 
	 } 
 }

细心的读者会发现,在上节的图 2 中,线程池的 startJob 方法里面调用了 JobManager 实例的 startJob 方法,这个在上节中我们没有介绍,我们把 JobManager 的 startJob 方法拿到本节 Job 调度这里来作介绍。在清单 7 中,startJob 方法着重调用了 nextJob 方法。 nextJob 方法删除并返回等待队列中的第一个 job,如果等待队列空了,该方法会返回 null 。如果一个 job 从该等待队列中删除掉了,则其是被加入到运行队列中去了。具体算法请看清单 8 。


清单 7. JobManager 的 startJob 方法
protected Job startJob() { 
	 while (true) { 
		 Job job =nextJob();if (job == null) 
			 return null; 
		 //must perform this outside sync block because it is third party code 
		 if (job.shouldRun()) { 
			 //check for listener veto 
			 jobListeners.aboutToRun(job); 
			 //listeners may have canceled or put the job to sleep 
			 if (job.getState() == Job.RUNNING) { 
				 ((InternalJob) job).setMonitor(createMonitor(job)); 
				 jobListeners.running(job); 
				 return job; 
			 } 
		 } 
		 if (job.getState() != Job.SLEEPING) { 
			 //job has been vetoed or canceled, so mark it as done 
			 endJob(job, Status.CANCEL_STATUS, true); 
			 continue; 
		 } 
	 } 
 }


清单 8. JobManager 的 nextJob 方法
private Job nextJob() { 
	 synchronized (lock) { 
		 //tickle the sleep queue to see if anyone wakes up 
		 long now = System.currentTimeMillis(); 
		 InternalJob job = sleeping.peek(); 
		 while (job != null && job.getStartTime() < now) { 
			 job.setStartTime(now + delayFor(job.getPriority())); 
			 changeState(job, Job.WAITING); 
			 job = sleeping.peek(); 
		 } 
		 //process the wait queue until we find a job whose rules are satisfied. 
		 while ((job = waiting.peek()) != null) { 
			 InternalJob blocker =findBlockingJob(job); 
			 if (blocker == null) 
				 break; 
			 //queue this job after the job that's blocking it 
			 changeState(job, InternalJob.BLOCKED); 
		//assert job does not already belong to some other data structure 
			 Assert.isTrue(job.next() == null); 
			 Assert.isTrue(job.previous() == null); 
			 blocker.addLast(job); 
		 } 
	 //the job to run must be in the running list before we exit 
	//the sync block, otherwise two jobs with conflicting rules could start at once 
		 if (job != null) { 
			 changeState(job, Job.RUNNING); 
			 if (JobManager.DEBUG) 
				 JobManager.debug("Starting job: " + job); //$NON-NLS-1$ 
		 } 
		 return (Job) job; 
	 } 
 }

在清单 8 中,程序首先检查 sleeping 队列中是否有醒来的 job,如果有的话,调用 changeState 方法把其放入到等待队列中。然后处理 waiting 队列中的 jobs,直到找到一个满足规则的 job,调用 changeState(job,Job.RUNNING),使其运行。

文章至此,那么一个 Job 得生命周期又是怎么样的呢?下面简单的介绍一下:

首先通过 job = new Job( “ jobName ” ){ … run(){ … } … }; 构建一个 Job 。然后设置它的优先级 ( 如清单 9 所示,其最终会调用 JobManager 实例的 setPriority 方法 ),接下来调用我们上文着重介绍得 schedule 方法,把 job 加入到 JobManager 的 waiting 队列或者 sleeping 队列。接下来,JobManager 使等待队列里的 jobs 在合适的时候被调度运行。运行的 job 会使用控制器(monitor.beginTask( “ status ” ,100))提供反馈信息,用户可以看到进度条里面的进度信息,可以查看状态栏里的状态信息等等。


清单 9. JobManager 的 setPriority 方法
protected void setPriority(InternalJob job, int newPriority) { 
	 synchronized (lock) { 
		 int oldPriority = job.getPriority(); 
		 if (oldPriority == newPriority) return; 
		 job.internalSetPriority(newPriority); 
		 //if the job is waiting to run, reshuffle the queue 
		 if (job.getState() == Job.WAITING) { 
			 long oldStart = job.getStartTime(); 
	 job.setStartTime(oldStart + (delayFor(newPriority) - delayFor(oldPriority))); 
			 waiting.resort(job); 
		 } 
	 } 
 }

本小节从 JobQueue 队列入手,延续上一节 schedule 方法的主线,结合 JobManager 内的 startJob,nextJob 和 changeState 等方法,介绍了 Jobs API 的 Job 调度相关的实现逻辑。本文并没有覆盖 Job 调度相关的所有 APIs(比如 sleep ,wakeUp 等方法), 窥豹唯见一斑,希望感兴趣的读者去进一步研读源代码。


并发模型-线程同步机制

多线程编程中,多个线程的同步与互斥是最重要的技术话题之一。

在本文的第一节中,我们知道 eclipse 设计了 ISchedulingRule 管理共享资源中的访问冲突;也设计了 ILock 可重入锁来管理共享资源的访问。

在 org.eclipse.core.internal.jobs 实现包内 Jobs API 提供了 OrderedLock 类。它实现了 ILock 接口和 ISchedulingRule 接口。一个线程要获取很多锁才能获得对排他性资源的访问,OrderedLock 类的实现机制则是按照一个严格的顺序来获取锁,从而避免了死锁的发生。(在操作系统课程中,这是避免死锁发生的算法之一。)Job API 还提供了 LockManager 锁管理类 , DeadlockDetector 死锁检测类以及信号量 Semaphore 类。为了管理信号量,API 甚至提供了自己的数据结构 Queue 类,它具有我们熟悉的队列的功能,然而它还有过人之处。 Queue 类的构造器方法有一 boolean 类型的参数 reuse,该参数指示当你把对象从队列中删除时,如何处置该对象。如果 reuse 的值为 false,那么在删除一个对象时,队列将不再持有该对象的句柄。如果 reuse 的值为 true 的话,可使用 getNextAvailableObject 方法获取一个用过的对象,给它重新赋值,并把它加入到队列中。 Queue 类这样设计有利于提高程序的性能,并具有通用性。

下面我们参考类图分享 Jobs API 的设计。


图 4. Jobs API 并发控制相关类图
Jobs API 并发控制相关类图

那么如何拿到一个 OrderedLock 实例呢? 我们在自己应用程序中,是可以使用 Jobs API 提供的 ILock 来管理临界资源的。我们需要应用 JobManager 来获取该对象锁。 ILock accessLock = Platform.getJobManager().newLock(),newLock 方法最终调用 LockManager 类的 newLock 方法获取一个新创建的 OrderedLock 的实例对象。在执行临界代码之前,调用 accessLock.acquire() 加锁。在离开临界区后,一定要调用 accessLock.release() 释放锁对象,允许其它的线程访问临界资源。

锁并不局限于某一个 Job,任何准备访问临界资源 ( 可能是某个 API,或者某一代码片段 ) 的 Jobs 都一定能获得该锁对象。也就是说,希望使用锁来控制对某一临界资源访问的 Jobs 必须是都拥有同一个对象锁。图 5 则是调用 acquire 方法的顺序图。在 doAcquire 方法里面调用了 LockManager 对象的 aboutToWait 方法(已经持有该对象锁的线程是否允许想要获得该对象锁的线程立即获得该锁,或者等到持有该锁的线程释放掉该锁。换句话说,该方法用以检测这个锁是共享锁还是排它锁)和 addLockWaitThread 方法(当前的线程不能立即获得锁,就应调用此方法,更新死锁检测图,检测是否有死锁发生,其代码如清单 10 所示)。然后调用信号量类的 acquire 方法,相当于操作系统课程中所讲的 PV 原语的 P 原语操作。


图 5. acquire 加锁顺序图
acquire 加锁顺序图

清单 10. LockManager 的 addLockWaitThread 方法
void addLockWaitThread(Thread thread, ISchedulingRule lock) { 
  synchronized (locks) { 
 // 给定线程不能够获得锁,而且正在等待该锁。更新死锁检测图
	locks.lockWaitStart(thread, lock); 
	if (locks.isDeadlocked()) { // 如果死锁发生了,解决之
	 Thread candidate = locks.resolutionCandidate(thread, lock); 
	 if (JobManager.DEBUG_LOCKS) 
		 locks.reportDeadlock(thread, lock, candidate); 
	 if (JobManager.DEBUG_DEADLOCK) 
	  throw new IllegalStateException("Deadlock detected. Caused by thread " + 
	    thread.getName() + '.');  
	ISchedulingRule[] toSuspend = locks.contestedLocksForThread(candidate); 
	 LockState[] suspended = new LockState[toSuspend.length]; 
	 for (int i = 0; i < toSuspend.length; i++) { 
		 locks.setToWait(candidate, toSuspend[i]); 
		 suspended[i] = LockState.suspend((OrderedLock) toSuspend[i]); 
	 } 
	 synchronized (suspendedLocks) { 
		 Stack prevLocks = (Stack) suspendedLocks.get(candidate); 
				 if (prevLocks == null) 
					 prevLocks = new Stack(); 
				 prevLocks.push(suspended); 
				 suspendedLocks.put(candidate, prevLocks); 
			 } 
			 locks.deadlockSolved(); 
		 } 
	 } 
 }

在很多应用 Job 的程序中,开发人员更多的是使用 ISchedulingRule 而不是直接使用 ILock 。如清单 11 所示。调用 setRule 不会影响第一个调用该方法的 job,比如清单 11 里面的 job1 。但是它会影响随后设置该 rule 准备运行的其它 Jobs,比如 job2 。 Jobs API 里面是如何实现的呢?非常细心的读者会发现清单 8 中的 findBlockingJob(job); 语句是粗体。在本节清单 12 给出该方法的实现逻辑。


清单 11. ISchedulingRule 应用
private static final ISchedulingRule SAMPLE_JOB_RULE = new ISchedulingRule() { 
	 public boolean contains(ISchedulingRule rule) { 
		 return this.equals(rule); } 
	 public boolean isConflicting(ISchedulingRule rule) { 
		 return this.equals(rule); } 
	 public String toString() { return "SAMPLE_JOB_RULE";} 
 }; 
 Job job1 = new Job( “ job1Name ” ){ … run(){ … } … } 
 Job job2 = new Job( “ job2Name ” ){ … run(){ … } … } 
 job1.setRule(SAMPLE_JOB_RULE); job1.schedule(); 
 job2.setRule(SAMPLE_JOB_RULE); job2.schedule();


清单 12. JobManager 的 findBlockingJob 方法
protected InternalJob findBlockingJob(InternalJob waiting) { 
	 if (waiting.getRule() == null) return null; 
	 synchronized (lock) { 
		 for (Iterator it = running.iterator(); it.hasNext();) { 
			 InternalJob job = (InternalJob) it.next(); 
			 //check the running job and all blocked jobs 
			 while (job != null) {if (waiting.isConflicting(job))
				return job;
				job = job.previous(); 
			 } 
		 } 
	 } 
	 return null; 
 }

在清单 12 程序中,遍历所有运行的 jobs,如果任何一个 job 同 waiting 队列里拿到的这个 job 设置了同一个调度规则(rule)的话,那么就返回这个 job(清单 8 中该变量名字是 blocker)。我们再看一下清单 8 程序的逻辑,如果返回这个 blocker 不为空,那么调用 changeState(job, InternalJob.BLOCKED); 方法改变 waiting 队列里此 job 的状态信息,并调用 blocker.addLast(job); 把其加入 blocker 的阻塞队列中 ( 当一个 job 执行完后,在 JobManager 的 endJob 方法里面,把该 job 所阻塞的 jobs 放入等待队列中 ) 。 这样程序就保证了,设置为同一调度规则的不同 jobs,不会在同一时间被调度,不会有访问共享资源的冲突。

本节大体上介绍了 Jobs API 并发控制所涉及的相关类,并从读者应用 ILock 锁的角度介绍了加锁的一系列实现逻辑,清单 10 也给出了解决死锁的逻辑实现。最后,本节亦从用户使用 ISchedulingRule 调度规则的角度介绍了 Jobs API 的一些实现逻辑。


小结

Eclipse 平台提供了一个一致的并发编程模型。该模型通过服务和事件监听机制丰富了多线程程序的并发处理概念。为开发人员更好的利用多线程机制写出高质量的代码做出了重要贡献。作者希望读者能从本篇文章中获益,比较深入的了解 Jobs API 内部实现机制,知其然,知道如何应用 Jobs API ;知其所以然,提高自己调试、分析程序的能力。(关于事件监听器机制(JobListener, LockListener),用户回馈(monitor)等在本文的后续文章中或有介绍)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值