Avoid synchronized(this) in Java?

后端 未结 22 1213
予麋鹿
予麋鹿 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:55

    I'll cover each point separately.

    1. Some evil code may steal your lock (very popular this one, also has an "accidentally" variant)

      I'm more worried about accidentally. What it amounts to is that this use of this is part of your class' exposed interface, and should be documented. Sometimes the ability of other code to use your lock is desired. This is true of things like Collections.synchronizedMap (see the javadoc).

    2. All synchronized methods within the same class use the exact same lock, which reduces throughput

      This is overly simplistic thinking; just getting rid of synchronized(this) won't solve the problem. Proper synchronization for throughput will take more thought.

    3. You are (unnecessarily) exposing too much information

      This is a variant of #1. Use of synchronized(this) is part of your interface. If you don't want/need this exposed, don't do it.

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

    The reason not to synchronize on this is that sometimes you need more than one lock (the second lock often gets removed after some additional thinking, but you still need it in the intermediate state). If you lock on this, you always have to remember which one of the two locks is this; if you lock on a private Object, the variable name tells you that.

    From the reader's viewpoint, if you see locking on this, you always have to answer the two questions:

    1. what kind of access is protected by this?
    2. is one lock really enough, didn't someone introduce a bug?

    An example:

    class BadObject {
        private Something mStuff;
        synchronized setStuff(Something stuff) {
            mStuff = stuff;
        }
        synchronized getStuff(Something stuff) {
            return mStuff;
        }
        private MyListener myListener = new MyListener() {
            public void onMyEvent(...) {
                setStuff(...);
            }
        }
        synchronized void longOperation(MyListener l) {
            ...
            l.onMyEvent(...);
            ...
        }
    }
    

    If two threads begin longOperation() on two different instances of BadObject, they acquire their locks; when it's time to invoke l.onMyEvent(...), we have a deadlock because neither of the threads may acquire the other object's lock.

    In this example we may eliminate the deadlock by using two locks, one for short operations and one for long ones.

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

    No, you shouldn't always. However, I tend to avoid it when there are multiple concerns on a particular object that only need to be threadsafe in respect to themselves. For example, you might have a mutable data object that has "label" and "parent" fields; these need to be threadsafe, but changing one need not block the other from being written/read. (In practice I would avoid this by declaring the fields volatile and/or using java.util.concurrent's AtomicFoo wrappers).

    Synchronization in general is a bit clumsy, as it slaps a big lock down rather than thinking exactly how threads might be allowed to work around each other. Using synchronized(this) is even clumsier and anti-social, as it's saying "no-one may change anything on this class while I hold the lock". How often do you actually need to do that?

    I would much rather have more granular locks; even if you do want to stop everything from changing (perhaps you're serialising the object), you can just acquire all of the locks to achieve the same thing, plus it's more explicit that way. When you use synchronized(this), it's not clear exactly why you're synchronizing, or what the side effects might be. If you use synchronized(labelMonitor), or even better labelLock.getWriteLock().lock(), it's clear what you are doing and what the effects of your critical section are limited to.

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

    If you've decided that:

    • the thing you need to do is lock on the current object; and
    • you want to lock it with granularity smaller than a whole method;

    then I don't see the a taboo over synchronizezd(this).

    Some people deliberately use synchronized(this) (instead of marking the method synchronized) inside the whole contents of a method because they think it's "clearer to the reader" which object is actually being synchronized on. So long as people are making an informed choice (e.g. understand that by doing so they're actually inserting extra bytecodes into the method and this could have a knock-on effect on potential optimisations), I don't particularly see a problem with this. You should always document the concurrent behaviour of your program, so I don't see the "'synchronized' publishes the behaviour" argument as being so compelling.

    As to the question of which object's lock you should use, I think there's nothing wrong with synchronizing on the current object if this would be expected by the logic of what you're doing and how your class would typically be used. For example, with a collection, the object that you would logically expect to lock is generally the collection itself.

    0 讨论(0)
  • 2020-11-22 02:00

    There seems a different consensus in the C# and Java camps on this. The majority of Java code I have seen uses:

    // apply mutex to this instance
    synchronized(this) {
        // do work here
    }
    

    whereas the majority of C# code opts for the arguably safer:

    // instance level lock object
    private readonly object _syncObj = new object();
    
    ...
    
    // apply mutex to private instance level field (a System.Object usually)
    lock(_syncObj)
    {
        // do work here
    }
    

    The C# idiom is certainly safer. As mentioned previously, no malicious / accidental access to the lock can be made from outside the instance. Java code has this risk too, but it seems that the Java community has gravitated over time to the slightly less safe, but slightly more terse version.

    That's not meant as a dig against Java, just a reflection of my experience working on both languages.

    0 讨论(0)
  • 2020-11-22 02:00

    My two cents in 2019 even though this question could have been settled already.

    Locking on 'this' is not bad if you know what you are doing but behind the scene locking on 'this' is (which unfortunately what synchronized keyword in method definition allows).

    If you actually want users of your class to be able to 'steal' your lock (i.e. prevent other threads from dealing with it), you actually want all the synchronized methods to wait while another sync method is running and so on. It should be intentional and well thought off (and hence documented to help your users understand it).

    To further elaborate, in the reverse you must know what you are 'gaining' (or 'losing' out on) if you lock on a non accessible lock (nobody can 'steal' your lock, you are in total control and so on...).

    The problem for me is that synchronized keyword in the method definition signature makes it just too easy for programmers not to think about what to lock on which is a mighty important thing to think about if you don't want to run into problems in a multi-threaded program.

    One can't argue that 'typically' you don't want users of your class to be able to do these stuff or that 'typically' you want...It depends on what functionality you are coding. You can't make a thumb rule as you can't predict all the use cases.

    Consider for e.g. the printwriter which uses an internal lock but then people struggle to use it from multiple threads if they don't want their output to interleave.

    Should your lock be accessible outside of the class or not is your decision as a programmer on the basis of what functionality the class has. It is part of the api. You can't move away for instance from synchronized(this) to synchronized(provateObjet) without risking breaking changes in the code using it.

    Note 1: I know you can achieve whatever synchronized(this) 'achieves' by using a explicit lock object and exposing it but I think it is unnecessary if your behaviour is well documented and you actually know what locking on 'this' means.

    Note 2: I don't concur with the argument that if some code is accidentally stealing your lock its a bug and you have to solve it. This in a way is same argument as saying I can make all my methods public even if they are not meant to be public. If someone is 'accidentally' calling my intended to be private method its a bug. Why enable this accident in the first place!!! If ability to steal your lock is a problem for your class don't allow it. As simple as that.

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