📜 ⬆️ ⬇️

Friday story about synchronized methods in the Thread class

Recently, I came across an interesting synchronization feature on the Thread class. I created a class that inherits Thread and used the lock object pattern for internal synchronization. At some point it seemed to me that the lock object inflates the code, and since I use the created stream from a very small number of places, I threw it out, replacing it with synchronized methods and wait / notify for this. If you want to know what came out of this little violation of the “code” - welcome under cat.

In general, nothing good happened. The program worked normally almost always, but from time to time CI began to behave incorrectly.
A quick look at the Thread class showed that the java synchronization mechanism is used in the Thread class itself, namely in the join method:
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } } 

Actually notify () itself in the class Thread is not called, but is called in the cpp-code after the end of the thread:
 static void ensure_join(JavaThread* thread) { // We do not need to grap the Threads_lock, since we are operating on ourself. Handle threadObj(thread, thread->threadObj()); assert(threadObj.not_null(), "java thread object must exist"); ObjectLocker lock(threadObj, thread); // Ignore pending exception (ThreadDeath), since we are exiting anyway thread->clear_pending_exception(); // It is of profound importance that we set the stillborn bit and reset the thread object, // before we do the notify. Since, changing these two variable will make JVM_IsAlive return // false. So in case another thread is doing a join on this thread , it will detect that the thread // is dead when it gets notified. java_lang_Thread::set_stillborn(threadObj()); // Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED. java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED); java_lang_Thread::set_thread(threadObj(), NULL); lock.notify_all(thread); // Ignore pending exception (ThreadDeath), since we are exiting anyway thread->clear_pending_exception(); } 

Thus, two problems are created - by doing notify (), if you are unlucky, you wake up the thread join, which is not scary, but you do not wake up your thread, which is scary.
And second, after the completion of the stream, notifyAll () is called on it that will wake up all your wait (), although you obviously would not want to.

In general, the moral of the Friday story is trivial - use the lock object, if there is at least a potential possibility that someone other than you will synchronize to the objects of your class. And if you inherit from someone else's class that is not Object, then such a probability definitely exists.

')

Source: https://habr.com/ru/post/169619/


All Articles