简体   繁体   中英

Java Synchronization Lock

When we say we lock on an object using the synchronized keyword, does it mean we are acquiring a lock on the whole object or only at the code that exists in the block?

In the following example listOne.add is synchronized, does it mean if another thread accesses listOne.get it would be blocked until the first thread gets out of this block? What if a second thread accesses the listTwo.get or listTwo.add methods on the instance variables of the same object when the first thread is still in the synchronized block?

List<String> listONe = new ArrayList<String>();
List<String> listTwo = new ArrayList<String>();

/* ... ... ... */

synchronized(this) {
    listOne.add(something);
}

The lock is on the object instance that you include in the synchronized block.

But take care! That object is NOT intrinsically locked for access by other threads. Only threads that execute the same synchronized(obj) , where obj is this in your example but could in other threads also be a variable reference, wait on that lock.

Thus, threads that don't execute any synchronized statements can access any and all variables of the 'locked' object and you'll probably run into race conditions.

Given the methods:

  public void a(String s) {
    synchronized(this) {
      listOne.add(s);
    }
  }

  public void b(String s) {
    synchronized(this) {
      listTwo.add(s);
    }
  }

  public void c(String s) {
      listOne.add(s);
  }

  public void d(String s) {
      synchronized(listOne) {
        listOne.add(s);
      }
  }

You can not call a and b at the same time, as they are locked on the same lock. You can however call a and c at the same time (with multiple threads obviously) as they are not locked on the same lock. This can lead to trouble with listOne.

You can also call a and d at the same time, as d is no different in this context from c. It does not use the same lock.

It is important that you always lock listOne with the same lock, and allow no access to it without a lock. If listOne and listTwo are somehow related and sometimes need updates at the same time / atomically you'd need one lock for access to both of them. Otherwise 2 separate locks may be better.

Of course, you'd probably use the relatively new java.util.concurrent classes if all you need is a concurrent list :)

Other threads will block only on if you have a synchronized block on the same instance. So no operations on the lists themselves will block.

synchronized(this) {

will only lock the object this . To lock and work with the object listOne :

synchronized(listOne){
    listOne.add(something);
}

so that listOne is accessed one at a time by multiple threads.

See: http://download.oracle.com/javase/tutorial/essential/concurrency/locksync.html

You need to understand that the lock is advisory and is not physically enforced. For example if you decided that you where going to use an Object to lock access to certain class fields, you must write the code in such a way to actually acquire the lock before accessing those fields. If you don't you can still access them and potentially cause deadlocks or other threading issues.

The exception to this is the use of the synchronized keyword on methods where the runtime will automatically acquire the lock for you without you needing to do anything special.

The Java Language specification defines the meaning of the synchronized statement as follows:

A synchronized statement acquires a mutual-exclusion lock (§17.1) on behalf of the executing thread, executes a block, then releases the lock. While the executing thread owns the lock, no other thread may acquire the lock.

 SynchronizedStatement:` synchronized ( Expression ) Block` 

The type of Expression must be a reference type, or a compile-time error occurs.

A synchronized statement is executed by first evaluating the Expression.

If evaluation of the Expression completes abruptly for some reason, then the synchronized statement completes abruptly for the same reason.

Otherwise, if the value of the Expression is null, a NullPointerException is thrown.

Otherwise, let the non-null value of the Expression be V. The executing thread locks the lock associated with V. Then the Block is executed. If execution of the Block completes normally, then the lock is unlocked and the synchronized statement completes normally. If execution of the Block completes abruptly for any reason, then the lock is unlocked and the synchronized statement then completes abruptly for the same reason.

Acquiring the lock associated with an object does not of itself prevent other threads from accessing fields of the object or invoking unsynchronized methods on the object. Other threads can also use synchronized methods or the synchronized statement in a conventional manner to achieve mutual exclusion.

That is, in your example

synchronized(this) {
    listOne.add(something);
}

the synchronized block does treat the object referred to by listOne in any special way, other threads may work with it as they please. However, it ensures that no other thread may enter a synchronized block for the object referred to by this at the same time. Therefore, if all code working with listOne is in synchronized blocks for the same object, at most one thread may work with listOne at any given time.

Also note that the object being locked on gets no special protection from concurrent access of its state, so the code

void increment() {
    synchronized (this) {
        this.counter = this.counter + 1;
    }
}

void reset() {
    this.counter = 0;
}

is incorrectly synchronized, as a second thread may execute reset while the first thread has read, but not yet written, counter , causing the reset to be overwritten.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM