मेरा प्रश्न इस प्रश्न से संबंधित है, जिसका उत्तर पहले से ही है:

हाँ, क्रियाओं के बीच एक होता है-पहले संबंध है थ्रेड कॉलिंग invokeLater/invokeAndWait और पर कार्रवाई रननेबल का EDT इस प्रकार प्रस्तुत किया गया।

मेरा प्रश्न थोड़ा अधिक सामान्य है: क्या invokeAndWait जैसी विधि को लागू करना भी संभव है, जैसे कि यह ठीक से काम करता है, लेकिन एक होता-पहले को लागू नहीं करता है एम> संबंध? ठीक से काम करने की विधि से मेरा तात्पर्य निम्न से है:

  • सबमिट किया गया Runnable ठीक एक बार निष्पादित होने की गारंटी है।
  • सबमिट किया गया Runnable एक विशिष्ट थ्रेड पर निष्पादित होता है।
  • यह विधि तब तक प्रतीक्षा करती है जब तक कि सबमिट किए गए Runnable का निष्पादन समाप्त नहीं हो जाता।
  • सबमिट किए गए Runnable के निष्पादन के समाप्त होने के बाद विधि की वापसी की गारंटी है।

मेरे लिए होता-पहले संबंध थोपे बिना इसे लागू करने का कोई तरीका नहीं है, या क्या मैं गलत हूं? यदि ऐसा है, तो कृपया एक उदाहरण कार्यान्वयन शामिल करें, जो इसे साबित करता है।

3
stonar96 23 अक्टूबर 2020, 12:26

1 उत्तर

सबसे बढ़िया उत्तर

यहां सबसे कठिन आवश्यकता है:

सबमिट किया गया Runnable ठीक एक बार निष्पादित होने की गारंटी है।

एक गैर-volatile (सादा) फ़ील्ड का उपयोग करने से कार्य कार्य को सबमिट करने वाले से निष्पादक को स्थानांतरित करने से पहले होता है संबंध नहीं बनेगा, बल्कि यह भी होगा इस बात की गारंटी नहीं है कि निष्पादक कार्य को बिल्कुल या सीमित समय में देखता है। संकलक उस क्षेत्र में असाइनमेंट को अनुकूलित करने में सक्षम होगा, या रनटाइम के दौरान निष्पादक थ्रेड केवल मुख्य मेमोरी के बजाय अपने कैश से मान पढ़ सकता है।

तो जावा 8 या उससे कम का उपयोग करने वाले कोड के लिए, मैं कहूंगा कि उत्तर "नहीं, ऐसी invokeAndWait विधि संभव नहीं है" (मूल कोड का उपयोग करने को छोड़कर)।

हालाँकि, Java 9 ने मेमोरी मोड अपारदर्शी जोड़ा। डौग ली द्वारा "JDK 9 मेमोरी ऑर्डर मोड का उपयोग करना" पेज, JEP 193 के लेखक (जिसने इस कार्यक्षमता को जोड़ा है) इसका विस्तार से वर्णन करता है। सबसे महत्वपूर्ण बात यह है कि अपारदर्शी मोड volatile से कमजोर है लेकिन फिर भी निम्नलिखित गारंटी प्रदान करता है:

  • प्रगति। लेखन अंततः दिखाई देता है।
    [...]
    उदाहरण के लिए निर्माण में जिसमें कुछ चर x का एकमात्र संशोधन अपारदर्शी (या मजबूत) मोड में लिखने के लिए है, X.setOpaque(this, 1), while(X.getOpaque(this)!=1){} में घूमने वाला कोई अन्य धागा अंततः समाप्त हो जाएगा।
    [...]
    ध्यान दें कि यह गारंटी प्लेन मोड में नहीं होती है, जिसमें स्पिन लूप अनंत रूप से लूप (और आमतौर पर) कर सकते हैं [...]

ऐसी invokeAndWait पद्धति को होता-पहले संबंध के बिना डिज़ाइन करते समय आपको यह भी विचार करना होगा कि थ्रेड प्रारंभ करने से पहले एक क्रिया होने से पहले उस थ्रेड में पहली क्रिया होती है (JLS §17.4.4 )। तो कार्रवाई के निर्माण से पहले कार्यकर्ता धागा शुरू किया जाना चाहिए।

इसके अतिरिक्त "final फ़ील्ड शब्दार्थ" (JLS 17.15.1) पर विचार करना होगा। जब invokeAndWait का कॉलर लैम्ब्डा एक्सप्रेशन के रूप में Runnable बनाता है, तो उस लैम्ब्डा द्वारा वेरिएबल्स को कैप्चर करने से (मेरी समझ में) अंतर्निहित final फील्ड सेमेन्टिक्स होता है।

यदि ऐसा है, तो कृपया एक उदाहरण कार्यान्वयन शामिल करें, जो इसे साबित करता है।

हार्डवेयर और समय पर निर्भर होने के कारण, उदाहरणों का उपयोग करके थ्रेड-सुरक्षा या होता है-पहले संबंधों को साबित करना या अस्वीकार करना मुश्किल है, यदि असंभव नहीं है। हालांकि, इसमें jcstress जैसे टूल मदद कर सकते हैं।

नीचे invokeAndWait के लिए होता-पहले संबंध के बिना एक (सरलीकृत) संभावित कार्यान्वयन है। ध्यान दें कि मैं जावा मेमोरी मॉडल से पूरी तरह परिचित नहीं हूं इसलिए कोड में त्रुटियां हो सकती हैं।

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

class OpaqueExecutor {
  // For simplicity assume there will every only be a single work task
  // So pending task being replaced by other task is not an issue
  private final AtomicReference<Runnable> nextTask = new AtomicReference<>();

  public OpaqueExecutor() {
    Thread worker = new Thread(() -> {
      while (true) {
        // Use getOpaque() to no create happens-before relationship
        Runnable task = nextTask.getOpaque();
        if (task == null) {
          // For efficiency indicate to the JVM that this is busy-waiting
          Thread.onSpinWait();
        } else {
          // Clear pending task; memory mode here does not matter because we only want
          // to guarantee that this thread does not see task again
          nextTask.setPlain(null);
          task.run();
        }
      }
    }, "Worker thread");
    worker.setDaemon(true);
    worker.start();
  }

  public void invokeLater(Runnable runnable) {
    // For simplicity assume that there is no existing pending task which could be
    // replaced by this
    // Use setOpaque(...) to not create happens-before relationship
    nextTask.setOpaque(runnable);
  }

  private static class Task implements Runnable {
    private final AtomicBoolean isFinished = new AtomicBoolean(false);
    // Must NOT be final to prevent happens-before relationship from
    // final field semantics
    private Runnable runnable;

    public Task(Runnable runnable) {
      this.runnable = runnable;
    }

    public void run() {
      try {
        runnable.run();
      } finally {
        // Use setOpaque(...) to not create happens-before relationship
        isFinished.setOpaque(true);
      }
    }

    public void join() {
      // Use getOpaque() to no create happens-before relationship
      while (!isFinished.getOpaque()) {
        // For efficiency indicate to the JVM that this is busy-waiting
        Thread.onSpinWait();
      }
    }
  }

  public void invokeAndWait(Runnable runnable) {
    Task task = new Task(runnable);
    invokeLater(task);
    task.join();
  }

  public static void main(String... args) {
    // Create executor as first step to not create happens-before relationship
    // for Thread.start()
    OpaqueExecutor executor = new OpaqueExecutor();

    final int expectedValue = 123;
    final int expectedNewValue = 456;

    class MyTask implements Runnable {
      // Must NOT be final to prevent happens-before relationship from
      // final field semantics
      int value;

      public MyTask(int value) {
        this.value = value;
      }

      public void run() {
        int valueL = value;
        if (valueL == expectedValue) {
          System.out.println("Found expected value");
        } else {
          System.out.println("Unexpected value: " + valueL);
        }

        value = expectedNewValue;
      }
    }

    MyTask task = new MyTask(expectedValue);
    executor.invokeAndWait(task);

    int newValue = task.value;
    if (newValue == expectedNewValue) {
      System.out.println("Found expected new value");
    } else {
      System.out.println("Unexpected new value: " + newValue);
    }
  }
}
2
Marcono1234 30 पद 2020, 00:12