java,在两个单独的方法中同步?

Upr*_*ted 0 java multithreading

我正在尝试在java中创建线程安全队列。我遇到过这个例子:

class ProducerConsumer<T> {
   private static final int BUFFER_MAX_SIZE = 42;
   private List<T> buffer = new LinkedList<>();

   synchronized void produce(T value) throws InterruptedException {
      while (buffer.size() == BUFFER_MAX_SIZE) {
         wait();
      }
      buffer.add(value);
      notify();
   }

   synchronized T consume() throws InterruptedException {
      while (buffer.size() == 0) {
         wait();
      }
      T result = buffer.remove(0);
      notify();
      return result;
   }
}
Run Code Online (Sandbox Code Playgroud)

我是java新手。据我了解,这两个“同步”关键字将防止每个方法内部发生争用,但当同时调用两个方法时则不会。例如,线程P调用product、locks方法,线程C调用consume、locks其他方法,然后一个尝试从列表中提取元素,另一个尝试插入元素,就会出现线程异常。

我的问题:这个例子坏了吗?

或者也许我错过了一些东西,但没关系。

Tur*_*g85 6

JLS, \xc2\xa717.1对于该机制非常明确:

\n
\n

...

\n

方法synchronized\xc2\xa78.4.3.6)在调用时自动执行锁定操作;在锁定操作成功完成之前,不会执行其主体。如果该方法是实例方法,它将锁定与调用该方法的实例关联的监视器(即,在this方法体执行期间将称为的对象)。如果该方法是,它会锁定与表示定义该方法的类的对象关联static的监视器。Class如果方法主体的执行完成,无论是正常还是突然,都会在同一监视器上自动执行解锁操作。

\n

...

\n
\n

因此,可以保证在一个对象上的某个时间点最多有一个线程正在执行 produce(...)consume()produce(...)在某一时间点,一个线程在一个对象上执行,而另一个线程consume()在同一对象上执行,这是不可能的。

\n

The call to wait() in consume() releases the intrinsic lock and blocks execution. The call to notify() in produce(...) notifies one wait()ing thread (if any), so it can fight for the lock as soon as the lock is released by the current owner. Notice that a call to notify() does not release the intrinsic lock. It just wakes up a wait()ing thread. This can be made observable with the following code snippet:

\n
class Ideone {\n  private static final Object lock = new Object();\n\n  public static void main(String[] args) {\n    printWithThreadNamePrefix("Start");\n    Thread waiter = new Thread(Ideone::waiter);\n    waiter.start();\n\n    // Give waiter some time to a) start and b) acquire the intrinsic lock\n    try {\n      Thread.sleep(500);\n    } catch (InterruptedException e) {\n    }\n\n    final Thread notifier = new Thread(Ideone::notifier);\n    notifier.start();\n\n    while (true) {\n      try {\n        waiter.join();\n        break;\n      } catch (InterruptedException e) {\n      }\n    }\n\n    printWithThreadNamePrefix("End");\n  }\n\n  private static void waiter() {\n    synchronized (lock) {\n      printWithThreadNamePrefix("Waiting...");\n      while (true) {\n        try {\n          lock.wait();\n          break;\n        } catch (InterruptedException e) {\n        }\n      }\n      printWithThreadNamePrefix("... done waiting");\n    }\n  }\n\n  private static void printWithThreadNamePrefix(String msg) {\n    System.out.println(String.format(\n        "%s: %s",\n        Thread.currentThread().getName(),\n        msg));\n  }\n\n  private static void notifier() {\n    synchronized (lock) {\n      printWithThreadNamePrefix("notifying");\n      lock.notify();\n      while (true) {\n      }\n    }\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Ideone demo

\n

The program will never terminate. Although thread two calls notify(), it then enters an endless loop, never actually releasing the intrinsic lock. Thus, one never has a chance to acquire the intrinsic lock, and the program "hangs" (it is neither a deadlock, nor a livelock, it simply cannot proceed).

\n

The things I recommend to change are:

\n
    \n
  • declare private List<T> buffer additionally as final
  • \n
  • call notifyAll() instead of notify() in order to wake all waiting threads (they will still execute sequentially, for details see this question by Sergey Mikhanov and its answers)
  • \n
\n