Sponsored Link •
|
Advertisement
|
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.
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 |
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 #48 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.
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 #44 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.
Sponsored Links
|