Avoid synchronized(this) in Java?

后端 未结 22 1181
予麋鹿
予麋鹿 2020-11-22 01:23

Whenever a question pops up on SO about Java synchronization, some people are very eager to point out that synchronized(this) should be avoided. Instead, they c

相关标签:
22条回答
  • 2020-11-22 01:41

    It depends on the situation.
    If There is only one sharing entity or more than one.

    See full working example here

    A small introduction.

    Threads and shareable entities
    It is possible for multiple threads to access same entity, for eg multiple connectionThreads sharing a single messageQueue. Since the threads run concurrently there may be a chance of overriding one's data by another which may be a messed up situation.
    So we need some way to ensure that shareable entity is accessed only by one thread at a time. (CONCURRENCY).

    Synchronized block
    synchronized() block is a way to ensure concurrent access of shareable entity.
    First, a small analogy
    Suppose There are two-person P1, P2 (threads) a Washbasin (shareable entity) inside a washroom and there is a door (lock).
    Now we want one person to use washbasin at a time.
    An approach is to lock the door by P1 when the door is locked P2 waits until p1 completes his work
    P1 unlocks the door
    then only p1 can use washbasin.

    syntax.

    synchronized(this)
    {
      SHARED_ENTITY.....
    }
    

    "this" provided the intrinsic lock associated with the class (Java developer designed Object class in such a way that each object can work as monitor). Above approach works fine when there are only one shared entity and multiple threads (1: N).
    N shareable entities-M threads
    Now think of a situation when there is two washbasin inside a washroom and only one door. If we are using the previous approach, only p1 can use one washbasin at a time while p2 will wait outside. It is wastage of resource as no one is using B2 (washbasin).
    A wiser approach would be to create a smaller room inside washroom and provide them one door per washbasin. In this way, P1 can access B1 and P2 can access B2 and vice-versa.

    washbasin1;  
    washbasin2;
    
    Object lock1=new Object();
    Object lock2=new Object();
    
      synchronized(lock1)
      {
        washbasin1;
      }
    
      synchronized(lock2)
      {
        washbasin2;
      }
    


    See more on Threads----> here

    0 讨论(0)
  • 2020-11-22 01:41

    I think points one (somebody else using your lock) and two (all methods using the same lock needlessly) can happen in any fairly large application. Especially when there's no good communication between developers.

    It's not cast in stone, it's mostly an issue of good practice and preventing errors.

    0 讨论(0)
  • 2020-11-22 01:42

    While I agree about not adhering blindly to dogmatic rules, does the "lock stealing" scenario seem so eccentric to you? A thread could indeed acquire the lock on your object "externally"(synchronized(theObject) {...}), blocking other threads waiting on synchronized instance methods.

    If you don't believe in malicious code, consider that this code could come from third parties (for instance if you develop some sort of application server).

    The "accidental" version seems less likely, but as they say, "make something idiot-proof and someone will invent a better idiot".

    So I agree with the it-depends-on-what-the-class-does school of thought.


    Edit following eljenso's first 3 comments:

    I've never experienced the lock stealing problem but here is an imaginary scenario:

    Let's say your system is a servlet container, and the object we're considering is the ServletContext implementation. Its getAttribute method must be thread-safe, as context attributes are shared data; so you declare it as synchronized. Let's also imagine that you provide a public hosting service based on your container implementation.

    I'm your customer and deploy my "good" servlet on your site. It happens that my code contains a call to getAttribute.

    A hacker, disguised as another customer, deploys his malicious servlet on your site. It contains the following code in the init method:

    synchronized (this.getServletConfig().getServletContext()) {
       while (true) {}
    }
    

    Assuming we share the same servlet context (allowed by the spec as long as the two servlets are on the same virtual host), my call on getAttribute is locked forever. The hacker has achieved a DoS on my servlet.

    This attack is not possible if getAttribute is synchronized on a private lock, because 3rd-party code cannot acquire this lock.

    I admit that the example is contrived and an oversimplistic view of how a servlet container works, but IMHO it proves the point.

    So I would make my design choice based on security consideration: will I have complete control over the code that has access to the instances? What would be the consequence of a thread's holding a lock on an instance indefinitely?

    0 讨论(0)
  • 2020-11-22 01:42

    I think there is a good explanation on why each of these are vital techniques under your belt in a book called Java Concurrency In Practice by Brian Goetz. He makes one point very clear - you must use the same lock "EVERYWHERE" to protect the state of your object. Synchronised method and synchronising on an object often go hand in hand. E.g. Vector synchronises all its methods. If you have a handle to a vector object and are going to do "put if absent" then merely Vector synchronising its own individual methods isn't going to protect you from corruption of state. You need to synchronise using synchronised (vectorHandle). This will result in the SAME lock being acquired by every thread which has a handle to the vector and will protect overall state of the vector. This is called client side locking. We do know as a matter of fact vector does synchronised (this) / synchronises all its methods and hence synchronising on the object vectorHandle will result in proper synchronisation of vector objects state. Its foolish to believe that you are thread safe just because you are using a thread safe collection. This is precisely the reason ConcurrentHashMap explicitly introduced putIfAbsent method - to make such operations atomic.

    In summary

    1. Synchronising at method level allows client side locking.
    2. If you have a private lock object - it makes client side locking impossible. This is fine if you know that your class doesn't have "put if absent" type of functionality.
    3. If you are designing a library - then synchronising on this or synchronising the method is often wiser. Because you are rarely in a position to decide how your class is going to be used.
    4. Had Vector used a private lock object - it would have been impossible to get "put if absent" right. The client code will never gain a handle to the private lock thus breaking the fundamental rule of using the EXACT SAME LOCK to protect its state.
    5. Synchronising on this or synchronised methods do have a problem as others have pointed out - someone could get a lock and never release it. All other threads would keep waiting for the lock to be released.
    6. So know what you are doing and adopt the one that's correct.
    7. Someone argued that having a private lock object gives you better granularity - e.g. if two operations are unrelated - they could be guarded by different locks resulting in better throughput. But this i think is design smell and not code smell - if two operations are completely unrelated why are they part of the SAME class? Why should a class club unrelated functionalities at all? May be a utility class? Hmmmm - some util providing string manipulation and calendar date formatting through the same instance?? ... doesn't make any sense to me at least!!
    0 讨论(0)
  • 2020-11-22 01:44

    Avoid using synchronized(this) as a locking mechanism: This locks the whole class instance and can cause deadlocks. In such cases, refactor the code to lock only a specific method or variable, that way whole class doesn't get locked. Synchronised can be used inside method level.
    Instead of using synchronized(this), below code shows how you could just lock a method.

       public void foo() {
    if(operation = null) {
        synchronized(foo) { 
    if (operation == null) {
     // enter your code that this method has to handle...
              }
            }
          }
        }
    
    0 讨论(0)
  • 2020-11-22 01:48

    A lock is used for either visibility or for protecting some data from concurrent modification which may lead to race.

    When you need to just make primitive type operations to be atomic there are available options like AtomicInteger and the likes.

    But suppose you have two integers which are related to each other like x and y co-ordinates, which are related to each other and should be changed in an atomic manner. Then you would protect them using a same lock.

    A lock should only protect the state that is related to each other. No less and no more. If you use synchronized(this) in each method then even if the state of the class is unrelated all the threads will face contention even if updating unrelated state.

    class Point{
       private int x;
       private int y;
    
       public Point(int x, int y){
           this.x = x;
           this.y = y;
       }
    
       //mutating methods should be guarded by same lock
       public synchronized void changeCoordinates(int x, int y){
           this.x = x;
           this.y = y;
       }
    }
    

    In the above example I have only one method which mutates both x and y and not two different methods as x and y are related and if I had given two different methods for mutating x and y separately then it would not have been thread safe.

    This example is just to demonstrate and not necessarily the way it should be implemented. The best way to do it would be to make it IMMUTABLE.

    Now in opposition to Point example, there is an example of TwoCounters already provided by @Andreas where the state which is being protected by two different locks as the state is unrelated to each other.

    The process of using different locks to protect unrelated states is called Lock Striping or Lock Splitting

    0 讨论(0)
提交回复
热议问题