The Artima Developer Community
Sponsored Link

Chapter 20 of Inside the Java Virtual Machine
Thread Synchronization
by Bill Venners

<<  Page 3 of 4  >>

Advertisement

Synchronization Support in the Instruction Set

As mentioned earlier, the language provides two built-in ways to identify monitor regions in your programs: synchronized statements and synchronized methods. These two mechanisms, which implement the mutual exclusion aspect of synchronization, are supported by the Java virtual machine's instruction set.

Synchronized Statements

To create a synchronized statement, you use the synchronized keyword with an expression that evaluates to an object reference, as in the reverseOrder() method below:

// On CD-ROM in file threads/ex1/KitchenSync.java
class KitchenSync {

    private int[] intArray = new int[10];

    void reverseOrder() {
        synchronized (this) {
            int halfWay = intArray.length / 2;
            for (int i = 0; i < halfWay; ++i) {
                int upperIndex = intArray.length - 1 - i;
                int save = intArray[upperIndex];
                intArray[upperIndex] = intArray[i];
                intArray[i] = save;
            }
        }
    }
    // ...
}

In the above case, the statements contained within the synchronized block will not be executed until a lock is acquired on the current object (this). If instead of a this reference, the expression yielded a reference to another object, the lock associated with that object would be acquired before the thread continued. If the expression yields a reference to an instance of class Class, the lock associated with the class is acquired.

Two opcodes, monitorenter and monitorexit, are used for synchronization blocks within methods. These opcodes are shown in the Table 20-1. Table 20-1. Monitors

Opcode Operand(s) Description
monitorenter none pop objectref, acquire the lock associated with objectref
monitorexit none pop objectref, release the lock associated with objectref

Table 20-1. Monitors

When monitorenter is encountered by the Java virtual machine, it acquires the lock for the object referred to by objectref on the stack. If the thread already owns the lock for that object, the count that is associated with the lock is incremented. Each time monitorexit is executed for the thread on the object, the count is decremented. When the count reaches zero, the monitor is released.

Here is the bytecode sequence generated by the reverseOrder() method of the KitchenSync class:

// First place the reference to the object to lock into local
// variable 1. This local variable will be used by both the
// monitorenter and monitorexit instructions.
 0 aload_0        // Push local var 0 (the this reference)
 1 astore_1       // Store into local var 1

// Now acquire the lock on the referenced object
                 // Push local var 1 (the this reference; the
 2 aload_1        // object to lock)
                 // Pop reference, acquire the lock
 3 monitorenter   // on referenced object

// The code of the synchronized block begins here. A thread will not
// execute the next instruction, aload_0, until a lock has been
// successfully acquired on the this reference above.
 4 aload_0        // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
 5 getfield #4 
 8 arraylength    // Pop array ref, push int array length
 9 iconst_2       // Push constant int 2
10 idiv          // Pop two ints, divide, push int result
                 // Pop int into local var 3:
11 istore_3      // int halfway = intArray.length/2;

// This is the start of the code for the for loop
12 iconst_0      // Push constant int 0
13 istore 4      // Pop into local var 2: int i = 0;
15 goto 65       // Jump to for loop condition check

// This is the start of the body of the for loop
18 aload_0       // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
19 getfield #4 
22 arraylength   // Pop array ref, push int array length
23 iconst_1      // Push constant int 1
24 isub          // Pop two ints, subtract, push int result
25 iload 4       // Push int at local var 4 (i)
27 isub          // Pop two ints, subtract, push int result
                 // Pop int into local var 5:
28 istore 5      // int upperindex = intArray.length - 1 - i;
30 aload_0       // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
31 getfield #4 
34 iload 5       // Push int at local var 5 (upperIndex)
36 iaload        // Pop index, arrayref, push int at arrayref[index]
                 // Pop into local var 6:
37 istore 6      // int save = intArray[upperIndex];
39 aload_0       // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
40 getfield #4 
43 iload 5       // Push int at local var 5 (upperIndex)
45 aload_0       // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
46 getfield #4 
49 iload 4       // Push int at local var 4 (i)
51 iaload        // Pop index, arrayref, push int at arrayref[index]
                 // Set arrayref[index] = value:
52 iastore       // intArray[upperIndex] = intArray[i];
53 aload_0       // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
54 getfield #4 
57 iload 4       // Push int at local var 4 (i)
59 iload 6       // Push int at local var 6 (save)
                 // Set arrayref[index] = value:
61 iastore       // intArray[i] = save;

// The body of the for loop is now done, this instruction does
// the incrementing of the loop variable i
62 iinc 4 1      // Increment by 1 int at local var 4: ++i;

// This is the for loop condition check:
65 iload 4       // Push int at local var 4 (i)
67 iload_3       // Push int at local var 3 (halfway)
                 // Pop two ints, compare, jump if less than to
68 if_icmplt 18  // top of for loop body: for (; i < halfway;)
// The code of the synchronized block ends here

// The next two instructions unlock the object, making it available
// for other threads. The reference to the locked object was stored
// in local variable 1 above.
71 aload_1            // Push local var 1 (the this reference)
72 monitorexit        // Pop ref, unlock object
73 return             // return normally from method

// This is a catch clause for any exception thrown (and not caught
// from within the synchronized block. If an exception is thrown,
// the locked object is unlocked, making it available for other
// threads.
74 aload_1            // Push local var 1 (the this reference)
75 monitorexit        // Pop ref, unlock object
76 athrow             // rethrow the same exception

// The exception table shows the "catch all" clause covers the
// entire synchronized block, from just after the lock is acquired
// to just before the lock is released.
Exception table:
from to target type
  4  71   74   any

Note that a catch clause ensures the locked object will be unlocked even if an exception is thrown from within the synchronized block. No matter how the synchronized block is exited, the object lock acquired when the thread entered the block will definitely be released.

Synchronized Methods

To synchronize an entire method, you just include the synchronized keyword as one of the method qualifiers, as in:

// On CD-ROM in file threads/ex1/HeatSync.java
class HeatSync {

    private int[] intArray = new int[10];

    synchronized void reverseOrder() {
        int halfWay = intArray.length / 2;
        for (int i = 0; i < halfWay; ++i) {
            int upperIndex = intArray.length - 1 - i;
            int save = intArray[upperIndex];
            intArray[upperIndex] = intArray[i];
            intArray[i] = save;
        }
    }
    // ...
}

The Java virtual machine does not use any special opcodes to invoke or return from synchronized methods. When the virtual machine resolves the symbolic reference to a method, it determines whether the method is synchronized. If so, the virtual machine acquires a lock before invoking the method. For an instance method, the virtual machine acquires the lock associated with the object upon which the method is being invoked. For a class method, it acquires the lock associated with the class to which the method belongs (it locks a Class object). After a synchronized method completes, whether it completes by returning or by throwing an exception, the virtual machine releases the lock.

Here are the bytecodes that javac generates for HeatSync's reverseOrder() method:

 0 aload_0       // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
 1 getfield #4 
 4 arraylength   // Pop array ref, push int array length
 5 iconst_2      // Push constant int 2
 6 idiv          // Pop two ints, divide, push int result
                 // Pop int into local var 1:
 7 istore_1      // int halfway = intArray.length/2;

// This is the start of the code for the for loop
 8 iconst_0      // Push int constant 0
 9 istore_2      // Pop into local var 2: int i = 0;
10 goto 54       // Jump to for loop condition check

// This is the start of the body of the for loop
13 aload_0       // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
14 getfield #4 
17 arraylength   // Pop array ref, push int array length
18 iconst_1      // Push constant int 1
19 isub          // Pop two ints, subtract, push int result
20 iload_2       // Push int at local var 2 (i)
21 isub          // Pop two ints, subtract, push int result
                 // Pop int into local var 3:
22 istore_3      // int upperindex = intArray.length - 1 - i;
23 aload_0       // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
24 getfield #4 
27 iload_3       // Push int at local var 3 (upperIndex)
28 iaload        // Pop index, arrayref, push int at arrayref[index]
                 // Pop into local var 4:
29 istore 4      // int save = intArray[upperIndex];
31 aload_0       // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
32 getfield #4 
35 iload_3       // Push int at local var 3 (upperIndex)
36 aload_0       // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
37 getfield #4 
40 iload_2       // Push int at local var 2 (i)
41 iaload        // Pop index, arrayref, push int at arrayref[index]
                 // Pop value, index, and arrayref,
                 // Set arrayref[index] = value:
42 iastore       // intArray[upperIndex] = intArray[i];
43 aload_0       // Push the object ref at loc var 0 (the this ref)
                 // Pop object ref, push ref to instance variable
                 // intArray
44 getfield #4 
47 iload_2       // Push int at local var 2 (i)
48 iload 4       // Push int at local var 4 (save)
                 // Pop value, index, and arrayref,
                 // Set arrayref[index] = value:
50 iastore       // intArray[i] = save;

// The body of the for loop is now done, this instruction does
// the incrementing of the loop variable i
51 iinc 2 1      // Increment by 1 int at local var 2: ++i;

// This is the for loop condition check:
54 iload_2       // Push int at local var 2 (i)
55 iload_1       // Push int at local var 1 (halfway)
                 // Pop two ints, compare, jump if less than to
56 if_icmplt 13  // top of for loop body: for (; i < halfway;)

59 return        // return (void) from method

If you compare these bytecodes with the ones shown earlier for KitchenSync's reverseOrder() method, you will see that these bytecodes are in effect those of KitchenSync with the support for entering and exiting the monitor removed. Instructions at offset 0 through 56 of HeatSync's bytecodes correspond to instructions at offset 4 through 68 of KitchenSync's bytecodes. Because HeatSync's reverseOrder() method doesn't need a local variable slot to store the reference to the locked object, the local variable positions used by each method are different. The function of the instructions themselves, however, match up exactly.

Another difference between the two reverseOrder() methods is that the compiler doesn't create an exception table for HeatSync's reverseOrder() method. In HeatSync's case, an exception table isn't necessary. When this method is invoked, the Java virtual machine automatically acquires the lock on the this object. If this method completes abruptly, just as if it completes normally, the virtual machine will release the lock on the this object automatically.

A synchronized class (static) method operates in the same way as the synchronized instance method shown in the example above. The one difference is that instead of acquiring a lock on this (as there is no this in a class method), the thread must acquire a lock on the appropriate Class instance.

<<  Page 3 of 4  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use