23
JSCOOP: A High-Level Concurrency Framework for Java Faraz Ahmadi Torshizi Jonathan S. Ostroff Richard F. Paige Kevin J. Doyle Jenna Lau Technical Report CSE-2008-09 December 22, 2008 Department of Computer Science and Engineering 4700 Keele Street Toronto, Ontario M3J 1P3 Canada

Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework for Java

Faraz Ahmadi Torshizi

Jonathan S. Ostroff

Richard F. Paige

Kevin J. Doyle

Jenna Lau

Technical Report CSE-2008-09

December 22, 2008

Department of Computer Science and Engineering

4700 Keele Street Toronto, Ontario M3J 1P3 Canada

Page 2: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework forJava

Faraz Ahmadi Torshizi1, Jonathan S. Ostroff2, Richard F. Paige3, Kevin J.Doyle4, and Jenna Lau4

1 University of Toronto, Toronto, Canada, M5S [email protected]

2 York University, Toronto, Canada, M3J [email protected]

3 University of York, Heslington, UK, York, YO10 [email protected] IBM, Toronto, Canada

[kjdoyle,jennalau]@ca.ibm.com

Abstract. SCOOP is a minimal extension to the sequential object-orientedprogramming model for concurrency. The extension consists of one key-word (separate) that avoids explicit thread declarations, synchronizedblocks, explicit waits, and eliminates data races and atomicity violationsby construction through a set of compiler rules. It attempts to guaranteefairness via use of a global scheduler. We present a new implementationof SCOOP for Java, called JSCOOP. JSCOOP introduces a new set of an-notations modeled after SCOOP keywords, as well as several core libraryclasses which provide the support necessary to implement the SCOOPsemantics. A prototype Eclipse plug-in allows for the creation of JSCOOPprojects. The plug-in does syntax checking and detects consistency prob-lems at compile time. The use of JSCOOP is demonstrated by an example.

1 Introduction

Concurrent programming is challenging, particularly for less experienced pro-grammers who must reconcile their understanding of programming logic withthe low-level mechanisms (like threads, monitors and locks) needed to ensuresafe, secure, and correct code. For example, in multi-core processing, hardwaredesigners are able to produce advanced designs and implementations. Thetools available to software designers for writing advanced code that exploitsmulti-core technology is limited; much of this language and library supportis still very low-level, and does not allow programmers to easily exploit, forexample, object-oriented (OO) programming techniques – the very techniquesthat are widely taught and understood by programmers. We argue that thereis a gap between the capabilities and needs of modern programmers, and thefacilities available for concurrent programming.

The existence of this gap motivates the need for sophisticated languages,tools and techniques for making it easier to build concurrent applications, par-

Page 3: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

2 Faraz Ahmadi Torshizi et al.

ticularly by abstracting away from platform details. A general principle for ac-complishing this makes use of a layered architecture for concurrent applications:the top layer provides programmer-accessible features that abstract away fromrun-time issues; the bottom layer provides run-time functionality needed forsupporting the low-level mechanisms needed for concurrent programming.

Simple Concurrent Object-Oriented Programming (SCOOP) [13, 9] has beendeveloped over the past fifteen years as a high-level framework for concurrentOO programming that supports this conceptual layered architecture. Specifi-cally, SCOOP abstracts from technical details of concurrent programming (e.g.,explicit locking when accessing shared objects), while providing a lightweightsyntactic extension to sequential object-oriented programming. In particular,SCOOP allows existing sequential code to be used seamlessly in combinationwith concurrent code while guaranteeing freedom from atomicity and race con-ditions by construction. This in part reduces the conceptual gap that program-mers have to cross when they migrate to concurrent programming, and alsoallows integration of new code with legacy applications.

SCOOP can be used with a variety of run-time models as illustrated inFig. 1. Application developers mostly see the higher-level programming layer.Automatic translation occurs under the hood to the underlying runtime. Themapping from conceptual processors to actual CPUs or to distributed proces-sors appears in a configuration file. The configuration data is used to configurethe run-time, e.g., by describing the number of logical and physical processorsused.

Thread-

Based

Run-Time

CORBA-

Based

Run-Time

PVM-

Based

Run-Time

Configuration

Data

Multicore-

Based

Run-Time

Fig. 1. Layered architecture for concurrent programming

In addition to offering a high-level concurrent programming model, SCOOPhas also been designed to provide infrastructure that will help programmersand verification teams establish that their concurrent programs satisfy desir-able correctness properties – e.g., deadlock freedom, weak/strong fairness, etc.While current SCOOP implementations do not currently guarantee that suchproperties are exhibited by programs through construction, the SCOOP designand underlying theory has been developed to allow verification of these prop-erties, either through intrinsic capabilities in SCOOP tooling (e.g., the compil-er/interpreter) or through interfacing with external verification tools such asmodel checkers.

Page 4: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework for Java 3

1.1 Contribution of this paper

Until now, the SCOOP model has been implemented solely for the Eiffel pro-gramming language, and has built on Eiffel’s powerful support for correctness-by-construction [8]. This paper contributes a new implementation of SCOOPfor Java, thus demonstrating that the SCOOP model is more generally applica-ble. Moreover, by implementing SCOOP for Java, we provide a more abstractconcurrency model to Java programmers, thus ideally making concurrent pro-gramming easier to carry out as well as leaving room for application to newhardware designs such as as multi-core.

Specifically, the contributions of this paper are:

– A prototype implementation of the SCOOP concurrency model for Java,called JSCOOP. JSCOOP uses two new keywords @separate and @await (inthe form of annotations) at the programming layer.

– A new library hides the low-level implementation details from the applica-tion programmer. A prototype translator maps JSCOOP programs into low-level pure Java programs (using Java’s thread mechanism) to execute the JS-COOP program.

– A prototype Eclipse plug-in allows programmers to create JSCOOP projects.The JSCOOP plug-in supports syntax highlighting and compile time consis-tency checking (for “traitors” that break the concurrency model). The Eclipseplugin uses the library to translate JSCOOP programs to executable code.Compile time errors are reported at the JSCOOP level using the Eclipse APIand GUI.

2 Motivating JSCOOP example and SCOOP background

In this section, we provide a motivating example of JSCOOP, and explain theSCOOP model more precisely. The following sections present the technical de-sign and implementation of JSCOOP and its supporting tools.

A snippet of a JSCOOP program for the dining philosophers is shown inListing 1. Attributes leftFork and rightFork are declared separate, mean-ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling thephilosopher object). Method calls from a philosopher to a fork object (e.g., topick the fork up) are handled by the fork’s dedicated processor in the orderthat the calls are received.

Philosophers deadlock when forks are picked up one at a time by competingphilosophers. Application programmers may avoid deadlock by recognizingthat two forks must be obtained at the same time for a philosopher to safely eatto completion. We encode this information in an eat method that takes a leftand right fork as separate arguments. The underlying runtime ensures that allseparate arguments of methods are reserved before proceeding to the body ofthe method. In addition, the runtime will check that the await condition holds

Page 5: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

4 Faraz Ahmadi Torshizi et al.

before allocating the requested resources. In our case, the wait condition assertsthat both forks must not be in use.

1 public class Philosopher {2 private @separate Fork rightFork;3 private @separate Fork leftFork;4 private int status; // 0:idle, 1:thinking, 2:eating56 public Philosopher (@separate Fork l, @separate Fork r) {7 left_fork = l;8 right_fork = r;9 }

1011 @await(pre="!l.isInUse()&&!r.isInUse()")12 public void eat(@separate Fork l, @separate Fork r){13 l.pickUp(); //separate call14 r.pickUp();15 if(l.isInUse() && r.isInUse()) {16 status = 2;17 }18 l.putDown();19 r.putDown();20 if(!l.isInUse() && !r.isInUse()) {21 status = 0;22 }23 }2425 public void live(){26 while(true) {27 status = 1;28 eat(left_fork, right_fork); //non-separate call29 }30 }31 }

Listing 1. Example of a JSCOOP program: Philosopher

A scheduling algorithm hidden from the application programmer ensuresthat resources are fairly allocated. Thus, philosophers must wait for resourcesto become available, but are guaranteed that eventually they will become avail-able provided that all reservation methods like eat terminate. Such methodsterminate provided they have no infinite loops and have reserved all relevantresources.

The example in Listing 1 thus illustrates some of the simplicity of the pro-gramming layer while providing guidance in reasoning about deadlock avoid-ance. The code in the listing does not need to use lower level thread and syn-chronization mechanisms such as monitors or semaphores. The underlying JS-COOP runtime scheduling algorithm also removes the burden on programmersof producing fair scheduling code themselves. A fair textbook example [5, p137]of the dining philosophers example is shown in the appendix which allows forcomparsion between ordinary Java and JSCOOP.

JSCOOP makes it easier to reuse of sequential libraries. One of the problemsthat makes it hard to develop multithreaded applications is the limited abil-ity to reuse sequential libraries. A naive reuse of library classes that have not

Page 6: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework for Java 5

been designed for concurrency often leads to data races and atomicity viola-tions. The mutual exclusion guarantees offered under the hood makes it pos-sible to assume a correct synchronisation of client calls and focus on solvingthe problem without bothering about the exact context in which a class will beused [13]. The Fork class (Listing 2) is an example of a sequential library, i.e., ithas no @await or @separate annotations and is used by the Philosopherclass. The main class of this system is shown in Listing 3. In line 4 we used thespecialized separate annotation for arrays to create an array object. In SCOOP,arrays can be treated as separate objects. Likewise, their contents can also bedeclared as separate. This results in the following options: a non-separate ar-ray with non-separate elements, a non-separate array with separate elements, aseparate array with non-separate elements, and a separate array with separateelements.

In order to provide the flexibility of declaring one of the four array specifi-cations listed above, a directive (dir) has been added to the @separate anno-tation. Developers are then free to use standard array notation with this addeddirective. The directive corresponding the above options is as follows:

1. Object[] o = new Object[#]2. @separate(dir=”Object[@separate] o”) Object[] o = new Object[#]3. @separate(dir=”@separate Object[] o”) Object[] o = new Object[#]4. @separate(dir=”@separate Object[@separate] o”) Object[] o = new Object[#]

Therefore, Line 4 creates an array object (handled by the current processor)that contains five separate fork elements (handled by other processors). Line 7creates a separate fork object causing creation of a new thread in the system.Similarly, lines 10–16 create an array that contains five separate philosophers.Method startPhilosopher starts individual philosophers.

1 public class Fork {2 private Boolean in_use;34 public Fork() {5 in_use = false;6 }78 public void putDown() {9 in_use = false;

10 }1112 public void pickUp() {13 in_use = true;14 }1516 public boolean isInUse() {17 return in_use;18 }19 }

Listing 2. The Fork class

Page 7: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

6 Faraz Ahmadi Torshizi et al.

1 public class DiningPhilosophers {2 public static void main(String[] args) {34 @separate(dir="Fork[@separate] forks") Fork[] forks = new Fork[5];5 //creating separate forks6 for(int i = 0; i < 5; i++) {7 @separate Fork fork = new Fork();8 forks[i] = fork;9 }

10 @separate(dir="Philosopher[@separate] philosophers")11 Philosopher[] philosophers = new Philosopher[5];12 //creating separate philosophers13 for(int i = 0; i < 5; i++) {14 @separate Philosopher phil =15 new Philosopher(forks[i],forks[(i+1)%5]);16 philosophers[i] = phil;17 startPhilosopher(phil); //starting the philosophers18 }19 }2021 private static void startPhilosopher(@separate Philosopher p) {22 p.live();23 }24 }

Listing 3. JSCOOP main class: DiningPhilosophers

2.1 Background: the SCOOP model

The SCOOP model was described in detail by Meyer [9], and refined (with aprototype implementation) by Nienaltowski [13]. The overall model presentsseveral useful properties: (a) it guarantees freedom from race conditions andatomicity violations by construction; (b) it guarantees fairness with respect toresource locking; and (c) it provides an easy way to program concurrent object-oriented application by hiding many of the cumbersome lower level implemen-tation details.

The SCOOP model is based on the basic concept of object oriented compu-tation: the method call of the form o.m(a), where m is a method called on anobject o passing argument(s) a. SCOOP adds the notion of a processor (handler)to the above concept. A processor is an abstract notion used to define behavior;operations defined by m are executed on o by a processor p. Processors can bemapped to virtual machine threads, OS threads, or even physical CPUs (in caseof multi-core systems). The notion of processor applies equally to sequentialor concurrent programs. In a sequential setting, there is only one processor inthe system and behaviour is synchronous. In a concurrent setting there is morethan one processor. As a result, a method call may continue without waiting forprevious calls to finish (i.e., behaviour is asynchronous).

By default, new objects are created on the same processor assigned to han-dle the root (this) object. To allow the developer to denote that an object ishandled by a different processor than the current one, SCOOP introduces theseparate keyword. If the separate keyword is used in the declaration of an ob-ject o (e.g., an attribute), a new processor pwill be created as soon as an instance

Page 8: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework for Java 7

of o is created. From that point on, all actions on o will be handled by processorp. Assignment of objects to processors does not change over time.

In SCOOP, method calls are divided into two categories:

– A non-separate call where the target of the call is not declared as separate. Thistype of call will be handled by the current processor. If arguments containseparate objects, locks will be acquired automatically on all separate argu-ments before the method is executed. Locks are kept during the execution ofthe method and are released after method is finished. Therefore, atomicity isguaranteed at the level of methods.

– A separate call. In the body of a non-separate method, one can safely callanother method on an object declared as separate in the arguments of themethod. This type of call where the target of the call is separate, is referred toas a separate call. Calls on separate objects will be handled by the processorother than the one handling the current object.

In order to guarantee atomicity and freedom of data races, SCOOP imple-ments automatic locking on all formal arguments of methods. The SCOOP com-piler enforces certain rules to achieve this. There are three types of synchroniza-tion in SCOOP programs:

1. Acquiring locks: SCOOP locks all separate arguments before executing themethod. There are different interpretations on what the scheduler shoulddo when only a partial set of locks can be acquired [3].

2. Wait conditions: the original SCOOP model in [9] was described in termsof Eiffel [4] which has strong support for Design by Contract [8]. In thismodel, preconditions become wait conditions.

3. Wait by necessity: separate calls execute asynchronously with respect tothe client, i.e., the client of a separate call does not need to wait for thecall to return and can continue to its next instruction. However, as soon asthe client makes a query call (i.e. method that does not return void) on theseparate object, it must wait for all previous separate calls to finish.

Recent refinements to SCOOP, its semantics, and its supporting tools havebeen reported. Ostroff et al [17] describe how to use contracts for both con-current programming and rich formal verification in the context of SCOOP forEiffel, via a specialised virtual machine, thus making it feasible to use modelchecking for property verification. Nienaltowski [14] presents a refined accesscontrol policy for SCOOP that potentially enable more parallelism and reducethe chances of deadlock. Nienaltowski also presents [12] a proof technique forconcurrent SCOOP programs, derived from proof techniques for sequentialprograms. Brooke [2] presents an alternative concurrency model with similari-ties to SCOOP that theoretically increases parallelism.

2.2 Related work

The Java with Annotated Concurrency (JAC) system [7] is very similar in in-tent and principle to our work on JSCOOP. JAC provides concurrency anno-tations – specifically, the @controlled and @compatible – that are applicable

Page 9: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

8 Faraz Ahmadi Torshizi et al.

to sequential program text. JAC is based on an active object model [16]. Un-like JSCOOP, JAC does not provide a wait/precondition construct, arguing in-stead that both waiting and exceptional behaviour are important for precon-ditions. Also, via the @compatible annotation, JAC provides means for iden-tifying methods whose execution can safely overlap (without race conditions),i.e., it provides mutual exclusion mechanisms. JAC also provides annotationsfor methods, so as to indicate whether calls are synchronous or asynchronous,or autonomous (in the sense of active objects). The former two annotations aresubsumed by SCOOP (and JSCOOP)’s separate annotation. Overall, JAC pro-vides additional annotations to SCOOP and JSCOOP, thus allowing a greaterdegree of customisability, while requiring more from the programmer in termsof annotation, and guaranteeing less in terms of safety (i.e., through the typesafety rules of [13]).

Morales [11] presents the design of a prototype of SCOOP’s separate an-notation for Java; however, preconditions and general design-by-contract wasnot considered, and the support for type safety and well-formedness was notconsidered.

SCOOP and JAC differently adopt ideas from active objects, which pro-vide a general purpose form of multitasking. An object can request servicesasynchronously, but once the request has been made, control returns to the re-quester. Once the task has completed, the requesting active object is sent theresult bu the operating system.

A modern abstract programming framework for concurrent or parallel pro-gramming is Cilk [1]; Cilk works by requiring the programmer to specify theparts of the program that can be executed safely and concurrently; the sched-uler then decides how to allocate work to (physical) processors. Cilk is alsobased, in principle, on the idea of annotation, this time of the C programminglanguage. There are a number of basic annotations, including mechanisms forannotate a procedure call so that it can (but doesn’t have to) operate in parallelwith other executing code. As well, barrier methods can be provided throughannotations so as to provide synchronisation points. Cilk is not yet object-oriented, nor does it provide design-by-contract mechanisms (though recentwork has examined extending Cilk to C++). It has a powerful execution sched-uler and run-time, and recent work is focusing on minimising and eliminatingdata race problems.

3 JSCOOP Design and Implementation

In this section we focus on the design and implementation of the JSCOOP corelibrary classes shown in Fig. 2; these form the foundation of the JSCOOP im-plementation. The core library provides support for essential mechanisms suchas processors, separate and non-separate calls, atomic locking of multiple re-sources, wait semantics, wait by necessity, and fair scheduling. The additionalpart of the implementation is the translation of JSCOOP code to threaded Java;

Page 10: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework for Java 9

this is done by JSCOOP’s supporting Eclipse plugin (described in the next sec-tion) so that the complexity is hidden from the user.

In SCOOP, method calls on each object is executed by its processor. Proces-sors are instances of the JSCOOP Processor class. Every processor has a local-call stack and a remote-call queue. The local stack is used for storing non-separatecalls made by this processor and the remote call queue is used for storing callsmade by other processors. A processor can add calls to the remote-call queueof another processor only when it has a lock on the receiving processor. Due tothe lack of an agent [10] mechanism in Java, all calls need to be parsed to extractmethod names, return type, arguments, and argument types. This informationis stored in the JSCOOP Call objects (acting as a wrapper for method calls).The queue and the stack are implemented as linked lists of JSCOOP Call ele-ments.

In JSCOOP the Runnable interface is implemented by classes whose in-stances are intended to be executed by a thread, e.g., JSCOOP Processor(whose instances run on a single thread acting as the “processor” executing op-erations) or the global scheduler JSCOOP Scheduler. In the core library thereis also the JSCOOP Runnable interface which extends the Runnable inter-face. This interface allows the rest of the JSCOOP core library to rely on certainmethods being present in the translated code, while working around the lackof support for multiple inheritance [10] in Java.

The JSCOOP Scheduler should be instantiated once for every JSCOOPapplication. This instance acts as the global resource scheduler, and is respon-sible for checking @await conditions and acquiring locks on processors. Thescheduler manages a global lock request queue where locking requests arestored. The execution of a method by a processor may result in creation of a callrequest (an instance of JSCOOP Call) and its addition to the global requestqueue. The scheduling algorithm used in the JSCOOP Scheduler is stronglyfair with respect to locking resources [13].

Every class that implements the Runnable interface must define a run()method. Starting the thread causes the object’s run() method to be called inthat thread. In the run() method, each JSCOOP Processor performs repeat-edly the following actions:

1. If there is an item on the call stack which has a wait condition or a separateargument, the processor sends a lock request (JSCOOP LockRequest) tothe global scheduler JSCOOP Scheduler and then blocks until the sched-uler sends back the “go-ahead” signal. A lock request maintains a list ofprocessors (corresponding to separate arguments of the method) that needto be locked as well as a Semaphore object which allows this processor toblock until it is signaled by the JSCOOP Scheduler.

2. If the remote call queue is not empty, the processor dequeues an item fromthe remote call queue and pushes it onto the local call stack.

3. If both the stack and the queue are empty, the processor waits for new re-quests to be enqueued by other processors.

Page 11: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

10 Faraz Ahmadi Torshizi et al.

������������������

����

�������������� ��������

������������ ������������ ���������������� �������� ��������

���� ���������

��������� ������������������������������������������������ ����� �����!���� ������������ �����!���� ������������

���� ������������""" ��������������""" ���#��������""" ���$����������""" ��%��������"""

���� ��������

���������������� �������������� ������������ ����� �����!���� ����

���� ��������������""" ����%����������""" �������#����""" ���$�������"""

���� ���

����������������� ���������%����&��� � �������&��� ���&���������� �����!���� ���������������

���'�����(���� �����&��� �����&����������� ����������� ����� ���#��$�������

���� �����������

���� �����!���� �����������������

���#���� ������� ����� ���$���������

���� �����������

������������������&��� ������������&��� �������������������������)!����������� �������*���

����"" ���� ������ ��%��

+,""-

++

,""-

+

+

+

,""-

+

,""-

+

+

�����������

Fig. 2. UML class diagram showing the main elements of JSCOOP

Page 12: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework for Java 11

3.1 Synchronization Mechanism

A synchronization mechanism is key to ensuring that the JSCOOP translationis sound. Java provides the synchronized keyword to allow synchronization ofmethods or code fragments on a variable and ensure atomicity of synchronizedactions. This is extremely useful, however, synchronizing individual code frag-ments is not enough to provide full synchronization across a JSCOOP applica-tion. Features of the java.util.concurrent package were utilized in orderto provide the additional synchronization needed. The Semaphore class pro-vided the mechanism necessary to signal blocking classes across threads with-out the need to obtain a lock on the blocking object. Objects are able to block bycalling acquire() on a Semaphore object. Objects are released when someother thread calls release() on that semaphore.

Each JSCOOP Processor maintains a lock semaphore that is used to pro-tect access to the remote call queue by other processors. A “call request” tothe scheduler contains a list of lock semaphores that are needed by the clientprocessor. The required locks are stored in the JSCOOP LockRequest objectwhich is passed to the JSCOOP Call wrapper. The scheduler then grabs therequested lock semaphores on behalf of the client processor and sends the “go-ahead” signal to the client processor. The client continues its execution and re-leases the lock semaphores as soon as the call is finished.

In addition to the lock semaphore, each JSCOOP Runnablemaintains a callsemaphore. The call semaphore is passed to all method calls made by this proces-sor, and serves multiple purposes. When a call is separate, the client processoris not required to wait for the call to be completed. The call is wrapped andsent to the remote call queue of the supplier processor. The client processor canthen continue its execution. However, when a call is non-separate or contain await condition, the client processor is required to wait for locks to be grantedor for the condition to become true before it can continue. In order to do this,the client processor calls acquire() on its call semaphore after submitting acall request. This causes waiting on the client processor side. The “go-ahead”signal by the scheduler releases the call semaphore of the waiting processor.

In order to show what happens in the system during a separate or non-separate call let’s consider the JSCOOP code in Listing 4. We assume thatprocessor-A (handling an object of type ClassA) is about to execute line 6. Sincemethod m involves two separate arguments (arg-B and arg-C), processor-A needs to acquire the lock semaphores of both processors handling objectsattached to these arguments (e.g. processor-B and processor-C). Figure 3 is asequence diagram illustrating actions that happen under the hood to exe-cute m. First, processor-A creates a request asking for lock-semaphore-B and lock-semaphore-C (i.e. locks associated with the above arguments) and sends thisrequest to the global scheduler. processor-A then blocks on its call-semaphore-Auntil it receives the go-ahead signal from the scheduler. Next, the scheduler ac-quires both lock-semaphore-B and lock-semaphore-C (if both are available at thesame time) and checks the wait condition. If this is successful, then it releasescall-semaphore-A therefore signaling processor-A to continue to the body of m.

Page 13: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

12 Faraz Ahmadi Torshizi et al.

1 public class ClassA2 {3 private @separate ClassB b;4 private @separate ClassC c;5 ...6 this.m (b, c);7 ...8 @await(pre="arg-B.checkCondition&&arg-c.checkCondition")9 public void m(@separate ClassB arg-B, @separate ClassC arg-C){

10 arg-B.f(); // separate call11 arg-C.g(); // separate call12 ...13 }14 ...15 }

Listing 4. Example of a non-separate method m and separate methods f and g

processor-A can then safely add separate calls f and g (lines 9 and 10) to theremote call queue of processor-B and processor-C, respectively. At the end ofmethod call, processor-A releases both lock-semaphore-B and lock-semaphore-C al-lowing other processors to access the remote call queues.

������

������

������

������

� �������

� �������

���������

���������

���������

���������

������

������

������

������

������� ��������������������������������

�������

�������

�������

�� ���

������� ����� �����

������� ����� �����

�� ���

�� ���

Fig. 3. Sequence diagram showing a non-separate call followed by two separate callsinvolving three processors A, B and C (processor is abbreviated as proc and semaphoreas sem)

The call semaphore described above is also used for query calls, wherewait by necessity is needed. After submitting a remote call to the appropriateJSCOOP Processor, the calling class blocks on the call semaphore. The callsemaphore is released by the executing object after the method has terminatedand its return has been stored in the call variable. Once the calling object hasbeen signaled, it can retrieve the return value from the JSCOOP Call.

Although processors do not have to wait for the completion of remote calls,some synchronization is needed to ensure the object performing the executionof the method call has the required call information before the processor con-

Page 14: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework for Java 13

tinues to process calls from its queue. With no synchronization, it is possible toskip over the execution of some calls if the next queued call is processed be-fore the first call’s execution has taken place. In order to provide synchroniza-tion in these cases, JSCOOP Processor can set a processor semaphore in theJSCOOP Call it is processing. In the run() method of JSCOOP Runnableclasses, the JSCOOP Call is pulled from the global variable and stored ina local variable. Once this action takes place, the running object signals theJSCOOP Processor to continue.

Semaphores were also used to eliminate busy waiting from several classesin the core JSCOOP library. Objects such as the JSCOOP Processor and theJSCOOP Scheduler run continuously, processing JSCOOP Calls stored ina private LinkedList. When these lists are empty, a sleep semaphore is usedto block. The sleep semaphore is released with status change (i.e., A newJSCOOP Call is available for processing).

Line JSCOOP123456

...public void live(){

...eat(left_fork, right_fork);...

}

Line Java78910111213141516171819202122232425262728293031323334353637

...public void live(){

Semaphore j_call_semaphore = new Semaphore(0);JSCOOP_Call j_call;JSCOOP_Call[] j_wait_calls;...//eat(left_fork, right_fork);j_req_locks = new LinkedList<JSCOOP_Processor>();j_req_locks.add(left_fork.getProcessor());j_req_locks.add(right_fork.getProcessor());j_lock_request = new JSCOOP_LockRequest(this, j_req_locks,

this.getProcessor().getLockSemaphore());

j_arg_types = new Class[2];j_arg_types[0] = left_fork.getClass();j_arg_types[1] = right_fork.getClass();j_args = new Object[2];j_args[0] = left_fork;j_args[1] = right_fork;j_call = new JSCOOP_Call("eat", j_arg_types, j_args,

null, j_lock_request, this, j_call_semaphore);

getProcessor().addLocalCall(j_call);

j_call_semaphore.acquire();

eat(left_fork, right_fork);scheduler.releaseLocks(j_call);...

}

Fig. 4. Mapping from Philosopher to JSCOOP Philosopher: calling eat method

Page 15: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

14 Faraz Ahmadi Torshizi et al.

3.2 Sample translation

In this section we show samples of the translation of the dining philosopherexample (shown in Listing 1) from JSCOOP source code to threaded Java.Figs. 4 and 5 show line by line translation snippets of the eat method of thePhilosopher class to the corresponding Java code JSCOOP Philosopher.The UML class diagram in Fig. 2 shows the relationship between this class andthe core library. Due to the space limits we only show the important parts ofthis translation. For full translation see the appendix.

All method calls in JSCOOP Objects must be evaluated upon translationto determine whether a call is non-separate, separate (remote), requires locks,or has a wait condition. If the call is non-separate, requires no locks, and hasno wait condition, it can be executed as-is. However, if the call is separate, re-quires locks, or has an @await, it must be wrapped in a JSCOOP Call objectand passed to the appropriate JSCOOP Processor. If the call is non-separate,or requires wait-by-necessity the client processor must be blocked on the callsemaphore.

Line 4 in Fig. 5 is an example of a non-separate call eatwhich takes two sep-arate arguments left fork and right fork. The call is non-separate sincethe processor handling the target of the call (this) is the current processor. Inorder to continue to the body of the eat method, the current processor needs to(a) have explicit locks on processors that handle objects attached to left forkand right fork and (b) the wait condition of eat must be true. The abovetwo operations are performed by the global scheduler JSCOOP Scheduler.The method call therefore needs to be wrapped as a JSCOOP Call object andpassed to the global scheduler. Also this call will eventually executed by thecurrent processor, therefore it needs to be added to the call stack of the currentprocessor.

The creation of a JSCOOP Callwrapper j call is done in line 27 in Fig. 5.In order to create such a wrapper we need to (a) capture the argument types andthe return type of this call (lines 21–26) because of the lack of agent mechanismin Java as mentioned above, (b) create a lock request object that contains thelist of locks required by this call, i.e., processors handling objects attached toleft fork and right fork (lines 15–18), and (c) the call semaphore associ-ated with this processor. The creation of the call request is done at lines 27–28.The call is then added to the stack of this processor at line 30. After this, the cur-rent processor blocks on its call semaphore (line 32) to receive the “go-ahead”signal from the scheduler. The method body can safely be executed (line 34) af-ter the scheduler issues the signal. After execution of the eat method, the locksemaphores of both arguments are released at line 35.

Fig. 4 shows the translation of the body of eatmethod. As mentioned in theprevious section, the eat method will only be executed when lock semaphoreson both processors handling the left and right forks are acquired by the sched-uler and the wait condition is satisfied. The await annotations is translated intoa boolean method checkPreconditions (lines 10–21 in Fig. 4) which returnstrue iff the condition is satisfied. This boolean method is called by the global

Page 16: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework for Java 15

Line JSCOOP123456789

@await(pre="!l.isInUse()&&!r.isInUse()")public void eat(@separate Fork l, @separate Fork r){

l.pickUp();r.pickUp();if(l.isInUse() && r.isInUse()) {

status = 2;}...

}...

Line Java101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475

public boolean checkPreconditions(){ ...

if(method_name.equals("eat")) {...if((((JSCOOP_Fork) args[0]).isInUse()==false) &&

(((JSCOOP_Fork) args[1]).isInUse()==false))return true;

elsereturn false;

}} else if(method_name.equals(...)) {...

}

public void eat(JSCOOP_Fork l, JSCOOP_Fork r){

JSCOOP_Call[] j_wait_calls;Semaphore j_call_semaphore = new Semaphore(0);JSCOOP_Call j_call;

//l.pickUp();j_req_locks = new LinkedList<JSCOOP_Processor>();j_lock_request = new JSCOOP_LockRequest(l, j_req_locks,

l.getProcessor().getLockSemaphore());

j_arg_types = null; j_args = null;j_call = new JSCOOP_Call("pickUp", j_arg_types, j_args,

null, j_lock_request, l, null);

l.getProcessor().addRemoteCall(j_call);

//r.pickUp();...//if(l.isInUse() && r.isInUse())j_wait_calls = new JSCOOP_Call[2];j_req_locks = new LinkedList<JSCOOP_Processor>();j_lock_request = new JSCOOP_LockRequest(l, j_req_locks,

l.getProcessor().getLockSemaphore());

j_arg_types = null;j_args = null;j_wait_calls[0] = new JSCOOP_Call("isInUse", j_arg_types,

j_args, Boolean.class, j_lock_request, l,j_call_semaphore);

j_req_locks = new LinkedList<JSCOOP_Processor>();j_lock_request = new JSCOOP_LockRequest(r, j_req_locks,

r.getProcessor().getLockSemaphore());

j_arg_types = null;j_args = null;j_wait_calls[1] = new JSCOOP_Call("isInUse", j_arg_types,

j_args, Boolean.class, j_lock_request, r,j_call_semaphore);

l.getProcessor().addRemoteCall(j_wait_calls[0]);r.getProcessor().addRemoteCall(j_wait_calls[1]);

//(wait by necessity)j_call_semaphore.acquire(2);

//Execute the if statement with returned valuesif((Boolean)j_wait_calls[0].getReturnValue() &&

(Boolean)j_wait_calls[0].getReturnValue())status = 2;

...}

Fig. 5. Mapping from Philosopher to JSCOOP Philosopher

Page 17: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

16 Faraz Ahmadi Torshizi et al.

scheduler right after the lock semaphores on all arguments are acquired. Thereason for using the ad hoc if-else clause in the checkPreconditionsmethod(lines 15 and 20) is that the translation code tries to avoid using reflection asmuch as possible. Using the reflection capabilities of Java makes it harder touse a model checker such as Java pathfinder [6].

The arguments l and r of type Fork are declared to be separate (i.e. theywill be running on a different processor). The Fork class is therefore translatedto the JSCOOP Fork (which implements the JSCOOP Runnable interface).This allows us to access the remote call queues of the processors associatedwith the forks. The separate call l.pickUp() is translated in lines 30–38. AJSCOOP Call is created (in this case with empty list of required locks) and isadded to the remote call queue of the processor associated with the left fork(line 38). Since l.pickUp() does not return any values and is separate, thecurrent processor does not need to wait for its finish and can continue to thenext instruction. The translation of the r.pickUp() is not shown for brevity.

Line 5 shows an example of two separate calls to the left and right Fork ob-jects l.isInUser() and r.isInUser() that require wait-by-necessity sincethey return boolean values. The client processor therefore needs to wait un-til calls are finished and then evaluate the condition of the if statement.Since both calls are separate, their translation (lines 43–65) is very similar tol.pickUp() (i.e., call requests are created and added to the remote call queuesof the processors handling l and r). The main difference is that the client pro-cessor is forced to wait for the results. This is achieved by calling acquire(2) (line68) on the call semaphore causing this processor to wait. The supplier proces-sors call release() on the call semaphore as soon as they are done with thecalls causing this processor to continue its execution. The if statement is trans-lated in lines 70–73 where the current processor can safely check the returnedvalues. The returned value is stored in the JSCOOP Call object.

4 Eclipse plug-in

Due to the difference between the semantics of separate and non-separate calls,it is necessary to check that a separate object is never assigned to a variable thatis declared as non-separate. Entities declared as non-separate but pointing toseparate objects are called traitors. It is important to detect traitors at compiletime so that we have a guarantee that remote objects cannot be executed exceptthrough the official locking mechanism that guaranteed freedom from atomic-ity and race violations. These checks for traitors and other syntax checking aredone by the Eclipse plug-in.

To eliminate the traitors, the SCOOP model provides the following sepa-rateness consistency rules (SC for short) [15].

– SC1: If the source of an attachment (assignment instruction or argumentpassing) is separate, its target entity must be separate too. This rule makes

Page 18: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework for Java 17

Fig. 6. Consistency rules

sure that the information regarding the processor of a source entity is pre-served in the assignment. As an example, line 10 in Fig. 6 is an invalid as-signment because its source x1 is separate but its target is not. Similarly, thecall in line 12 is invalid because the actual argument is separate while the cor-responding formal argument is not. There is no rule prohibiting attachmentsin the opposite direction from nonseparate to separate entities (e.g. line 11 isvalid).

– SC2: If an actual argument of a separate call is of a reference type, the cor-responding formal argument must be declared as separate. This rule ensures

Page 19: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

18 Faraz Ahmadi Torshizi et al.

that a non-separate reference passed as actual argument of a separate call beseen as separate outside the processor boundary. Let’s assume that methodf of class X (line 23 in Fig. 6) takes a non-separate argument and method gtakes a separate argument (line 24). The client is not allowed to use its non-separate attribute a as actual argument of x.f because from the point of viewof x, a is separate. On the other hand, the call x.g (a) is valid.

– SC3: If the source of an attachment is the result of a separate call to a functionreturning a reference type, the target must be declared as separate. If functionq of class X returns a reference, that result should be considered as separatewith respect to the client object. Therefore, assignment in line 35 is valid whilethe assignment in line 36 is invalid.

– SC4: If an actual argument or result of a separate call is of an expanded type,its base class may not include, directly or indirectly, any non-separate at-tribute of a reference type. This rule is not applicable to Java since there isno way for the user to define an expanded class.

A prototype Eclipse plug-in provides syntax checking for the input JSCOOPcode. The plug-in consists of two packages.

1. edu.yorku.jscoop: This package is responsible for correctness checkingand GUI support for JSCOOP Project creation. A single visitor class is usedto parse JSCOOP files for problem determination

2. edu.yorku.jscoop.translator: This package is responsible for theautomated translation of JSCOOP code to Java code. Basic framework forautomated translation has been designed with the anticipation of future de-velopment to fully implement this feature.

The plug-in reports errors to the editor on: incorrect placement of annota-tions, incorrect use of directive (dir), and partial checks for violation of SCOOPconsistency rules. We use the Eclipse AST Parser to isolate methods decoratedby an @await annotation. Wait conditions passed to @await as strings aretranslated into a corresponding assert statement which is used to detect prob-lems. As a result, all valid assert conditions compile successfully when passedto @await in the form of a string, while all invalid assert conditions are markedwith a problem. For example, on the @await(pre="x=10") input, our tool re-ports “Type mismatch: cannot convert from Integer to boolean.”

The Eclipse AST Parser is also able to isolate @separate annotations. Weare able to use the Eclipse AST functionalities to type check the argumentspassed in the method call by comparing them to the method declaration. Sep-arateness correctness rules SC1 and SC2 are checked when the JSCOOP code iscompiled using this plug-in. One of the following four error messages is dis-played in the Eclipse environment depending on the context of the problem: (a)arg name must be non-separate, (b) arg name must be non-separate to caller(c) Type mismatch: cannot convert from separate to non-separate (d) Type mis-match: non-separate return is separate to this class. Fig. 6 is an illustration ofthese compile time checks.

Page 20: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework for Java 19

In order to process the JSCOOP annotations, we have created a hook into theJDT Compilation Participant. The JSCOOP CompilationParticipant actson all JSCOOP Projects, and uses the JSCOOP Visitor to visit and process alloccurrences of @separate and @await in the source code. In order to process@await, we need to ensure that all preconditions passed as a string to the an-notation are valid. In order to do this, we first obtain the abstract syntax treefrom the original JSCOOP source files. From there, all @await statements aretranslated into assert statements, and rewritten to a new abstract syntax tree,which is in turn written to a new file. This file is then traversed for any compi-lation problems associated with the assert statements, which are then reflectedback to the original JSCOOP source file for display to the developer (see Fig. 7).

Fig. 7. Processing of @await annotations

5 Conclusion and future work

We have presented a new implementation of SCOOP for Java, based on theuse of Java annotations. A set of library classes and a preprocessor serve as animplementation, and allow co-existence of annotated and concurrent JSCOOPclasses with unannotated and sequential Java classes. We demonstrated theusefulness and simplicity of the JSCOOP annotation approach, and the overalltranslation from annotated Java to pure Java, that forms the basis of our imple-mentation. We also briefly discussed a set of simple Eclipse-based developmenttools that help to support the developer in using JSCOOP.

Our implementation of JSCOOP needs further extension to resolve two keyfundamental issues: deadlock detection and fairness. We aim to support these

Page 21: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

20 Faraz Ahmadi Torshizi et al.

through connections to model checkers and theorem provers. The fact that JS-COOP programs are annotated Java programs means we can exploit existingJava formal methods tools, particularly JML and Java Pathfinder. We intendto provide better integrated Eclipse support for connecting JSCOOP, JML, andJava Pathfinder so that the results of analysis are presented in terms of JSCOOPand its annotations, and the analysis engines are hidden from the user.

A further technical extension is to fully work through the relationship be-tween JSCOOP and class extension in Java; this is not fully implemented in theJSCOOP toolset so far.

References

1. Robert Blumofe, Christopher Joerg, Bradley Kuszmaul, Charles Leiserson, KeithRandall, and Yuli Zhou. Cilk: an efficient multithreaded runtime system. In Proc.Principles and Practice of Programming. ACM Press, 1995.

2. Phillip J. Brooke and Richard F. Paige. Cameo: an alternative concurrency model foreiffel. Formal Aspects of Computing, 2009. to appear.

3. Phillip J. Brooke, Richard F. Paige, and Jeremy L. Jacob. A CSP model of Eiffel’sSCOOP. Formal Aspects of Computing, 19(4):487–512, 2007.

4. ECMA. Eiffel: Analysis, design and programming language. Standard ECMA-367(2nd edition), June 2006.

5. Stephen J. Hartley. Concurrent programming: the Java programming language. OxfordUniversity Press, Inc., New York, NY, USA, 1998.

6. K. Havelund and T. Pressburger. Model checking Java programs using Javapathfinder. Software Tools for Technology Transfer (STTT), 2(4):72–84, 2000.

7. Klaus-Peter L’̈ohr and Max Haustein. The JAC system: Minimizing the differencesbetween concurrent and sequential Java code. Journal of Object Technology, 5(7), 2006.

8. Bertrand Meyer. Design by Contract. Technical Report TR-EI-12/CO, InteractiveSoftware Engineering Inc., 1986.

9. Bertrand Meyer. Object-Oriented Software Construction. Prentice Hall, 1997.10. Bertrand Meyer. Practice To Perfect: The Quality First Model. Computer, 1997. Prac-

tice To Perfect: The Quality First Model.11. Francisco Morales. Eiffel-like separate classes. Java Developer Journal, 2000.12. P. Nienaltowski, B. Meyer, and J.S. Ostroff. Contracts for concurrency. Formal Aspects

of Computing, 2008.13. Piotr Nienaltowski. Practical framework for contract-based concurrent object-oriented pro-

gramming, PhD thesis 17031. PhD thesis, Department of Computer Science, ETHZurich, 2007.

14. Piotr Nienaltowski. Flexible access control policy for SCOOP. Formal Aspects ofComputing, 2008.

15. Piotr Nienaltowski and Bertrand Meyer. Contracts for concurrency. Formal Aspectsof Computing (to appear), 2007.

16. Oscar Nierstrasz. Regular types for active objects. In OOPSLA, pages 1–15, 1993.17. Jonathan S. Ostroff, Faraz Torshizi, Hai Feng Huang, and Bernd Schoeller. Beyond

contracts for concurrency. Formal Aspects of Computing, 2008.

Page 22: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

JSCOOP: A High-Level Concurrency Framework for Java 21

Appendix

A textbook example [5, p137] of a fair Java solution to the dining philosophers is pro-vided below. The example below deals with lower level threads, synchonized blocks,and wait and notify constructs. It uses a dining server and an array of philosopher statesto maintain fairness. In JSCOOP, fairness happens “under the hood” with a global syn-chornizer.

1 public class DiningPhilosophers {2 public static void main(String[] args) {3 int numPhilosophers = 5;4 boolean checkStarving = true;5 DiningServer ds = new DiningServer(numPhilosophers, checkStarving);6 Philosopher[] philosophers = new Philosopher[numPhilosophers];7 for (int i = 0; i < numPhilosophers; i++) {8 philosophers[i] = new Philosopher("Philosopher", i, ds);9 philosophers[i].start();

10 }11 }12 }

Listing 5. Deadlock-free version of the main Java class for dining philosophers

1 public class Philosopher extends Thread {2 private int id = 0;3 private DiningServer ds = null;4 public Philosopher(String name, int id, DiningServer ds) {5 this.id = id; this.ds = ds;6 }78 private void think();9 private void eat();

1011 public void run() {12 while (true) {13 think();14 ds.takeForks(id);15 eat();16 ds.putForks(id);17 }18 }19 }

Listing 6. Deadlock-free version of the Java class Philosopher¸

Page 23: Technical Report CSE-2008-09 · ing that each fork object is handled by its own processor (i.e., thread of con-trol) separate from the current processor (in this case the thread handling

22 Faraz Ahmadi Torshizi et al.

1 class DiningServer {2 private boolean checkStarving = false;3 private int numPhils = 0;4 private int[] state = null;5 private static final int6 THINKING = 0, HUNGRY = 1, STARVING = 2, EATING = 3;78 public DiningServer(int numPhils, boolean checkStarving) {9 this.numPhils = numPhils;

10 this.checkStarving = checkStarving;11 state = new int[numPhils];12 for (int i = 0; i < numPhils; i++) state[i] = THINKING;13 System.out.println("DiningServer: checkStarving="14 + checkStarving);15 }1617 private final int left(int i) { return (numPhils + i - 1) % numPhils; }18 private final int right(int i) { return (i + 1) % numPhils; }1920 private void seeIfStarving(int k) {21 if (state[k] == HUNGRY && state[left(k)] != STARVING &&22 state[right(k)] != STARVING) {23 state[k] = STARVING;24 System.out.println("philosopher " + k + "is STARVING");25 }26 }2728 private void test(int k, boolean checkStarving) {29 if (state[left(k)] != EATING && state[left(k)] != STARVING &&30 (state[k] == HUNGRY || state[k] == STARVING) &&31 state[right(k)] != STARVING && state[right(k)] != EATING)32 state[k] = EATING;33 else if (checkStarving)34 seeIfStarving(k); // simplistic naive check for starvation35 }3637 public synchronized void takeForks(int i) {38 state[i] = HUNGRY;39 test(i, false);40 while (state[i] != EATING)41 try {wait();} catch (InterruptedException e) {}42 }4344 public synchronized void putForks(int i) {45 state[i] = THINKING;46 test(left(i), checkStarving);47 test(right(i), checkStarving);48 notifyAll();49 }50 }

Listing 7. Deadlock-free version of the Java monitor class