it-swarm.dev

مثال أبسط ومفهوم للكلمة الرئيسية المضطربة في Java

أنا أقرأ عن متقلبة كلمة أساسية في جافا وأفهم تماما جزء النظرية منها.

ولكن ، ما أبحث عنه ، مثال جيد على الحالة ، يوضح ما يمكن أن يحدث إذا لم يكن المتغير متغير وإذا كان كذلك.

أدناه رمز المقتطف لا يعمل كما هو متوقع (مأخوذ من هنا ):

class Test extends Thread {

    boolean keepRunning = true;

    public void run() {
        while (keepRunning) {
        }

        System.out.println("Thread terminated.");
    }

    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println("keepRunning set to false.");
    }
}

من الناحية المثالية ، إذا لم يكن keepRunning متقلبة ، فيجب أن يستمر مؤشر الترابط في العمل إلى أجل غير مسمى. ولكن ، لا يتوقف بعد بضع ثوان.

لدي سؤالان أساسيان:

  • يمكن لأي شخص أن يفسر متقلبة مع مثال؟ ليس مع نظرية من JLS.
  • هو بديل متقلبة للمزامنة؟ هل يحقق الذرية؟
64
tmgr

متقلبة -> تضمن الرؤية وليس الذرية

التزامن (القفل) -> يضمن الرؤية والقدرة الذرية (إذا تم ذلك بشكل صحيح)

التقلب ليس بديلاً للمزامنة

استخدم متقلبة فقط عندما تقوم بتحديث المرجع وعدم تنفيذ بعض العمليات الأخرى على ذلك.

مثال:

volatile int i = 0;

public void incrementI(){
   i++;
}

لن يكون مؤشر الترابط آمنًا دون استخدام المزامنة أو AtomicInteger حيث أن الزيادة هي عملية مركبة.

لماذا لا يعمل البرنامج إلى أجل غير مسمى؟

حسنا هذا يعتمد على الظروف المختلفة. في معظم الحالات ، يكون JVM ذكيًا بما يكفي لمسح المحتويات.

الاستخدام الصحيح للمتقلبة يناقش الاستخدامات المختلفة المحتملة للمتقلبة. استخدام المتقلبة بشكل صحيح أمر صعب ، وأود أن أقول "عندما تكون في شك ، اتركها" ، استخدم كتلة متزامنة بدلاً من ذلك.

أيضا:

يمكن استخدام الكتلة المتزامنة بدلاً من التقلب لكن العكس ليس صحيحًا .

45
Narendra Pathai

على سبيل المثال الخاص بك: إذا لم يتم الإعلان عن تقلبه ، يمكن أن يقوم الخادم JVM برفع المتغير keepRunning خارج الحلقة لأنه لم يتم تعديله في الحلقة (تحويله إلى حلقة لانهائية) ، ولكن العميل JVM لن . هذا هو السبب في أنك ترى نتائج مختلفة.

شرح عام حول المتغيرات المتقلبة يتبع:

عندما يتم الإعلان عن حقل volatile ، يتم إعلام المترجم ووقت التشغيل بأن هذا المتغير مشترك وأن العمليات عليه لا ينبغي إعادة ترتيبها بعمليات ذاكرة أخرى. لا يتم التخزين المؤقت للمتغيرات المتقلبة في السجلات أو في ذاكرات التخزين المؤقت حيث تكون مخفية عن المعالجات الأخرى ، لذلك قراءة متغير متغير دائمًا تُرجع دائمًا أحدث كتابة بواسطة أي مؤشر ترابط .

تتجاوز تأثيرات الرؤية للمتغيرات المتطايرة قيمة المتغير المتغير نفسه. عندما يكتب مؤشر الترابط A إلى متغير متغير وبالتالي يقرأ مؤشر الترابط B نفس المتغير ، تصبح قيم جميع المتغيرات التي كانت مرئية لـ A قبل الكتابة إلى المتغير المتغير مرئية لـ B بعد قراءة المتغير المتغير.

الاستخدام الأكثر شيوعًا للمتغيرات المتقلبة هو علامة إكمال أو مقاطعة أو علامة:

  volatile boolean flag;
  while (!flag)  {
     // do something untill flag is true
  }

يمكن استخدام المتغيرات المتقلبة لأنواع أخرى من معلومات الحالة ، ولكن يلزم مزيد من الحذر عند محاولة ذلك. على سبيل المثال ، فإن دلالات المتقلبة ليست قوية بما يكفي لجعل عملية الزيادة (count++) الذرية ، إلا إذا كنت تستطيع أن تضمن أن المتغير هو مكتوب فقط من موضوع واحد.

قفل يمكن أن يضمن كل من الرؤية والذرية. يمكن للمتغيرات المتقلبة ضمان الرؤية فقط.

يمكنك استخدام المتغيرات المتقلبة فقط عند استيفاء جميع المعايير التالية:

  • لا تعتمد الكتابة إلى المتغير على قيمتها الحالية ، أو يمكنك التأكد من قيام مؤشر ترابط واحد فقط بتحديث القيمة ؛
  • لا يشارك المتغير في المتغيرات مع متغيرات الحالة الأخرى؛ و
  • القفل غير مطلوب لأي سبب آخر أثناء الوصول إلى المتغير.

تلميح التصحيح : تأكد دائمًا من تحديد رمز تبديل سطر الأوامر -server JVM عند استدعاء JVM ، حتى للتطوير والاختبار. ينفذ خادم JVM أمثلية أكثر من عميل JVM ، مثل رفع متغيرات خارج حلقة لا يتم تعديلها في الحلقة ؛ الكود الذي قد يبدو أنه يعمل في بيئة التطوير (عميل JVM) يمكن أن يكسر بيئة النشر (خادم JVM).

هذا مقتطف من "Java Concurrency in Practice" ، وهو أفضل كتاب يمكنك العثور عليه في هذا الموضوع.

25
Sokolov

لقد قمت بتعديل المثال الخاص بك قليلا. الآن استخدم المثال مع keepRunning كعضو متقلبة وغير متقلبة:

class TestVolatile extends Thread{
    //volatile
    boolean keepRunning = true;

    public void run() {
        long count=0;
        while (keepRunning) {
            count++;
        }

        System.out.println("Thread terminated." + count);
    }

    public static void main(String[] args) throws InterruptedException {
        TestVolatile t = new TestVolatile();
        t.start();
        Thread.sleep(1000);
        System.out.println("after sleeping in main");
        t.keepRunning = false;
        t.join();
        System.out.println("keepRunning set to " + t.keepRunning);
    }
}
13
paritosht

ما هي الكلمة المتقلبة؟

متغير الكلمة الأساسية يمنع caching of variables.

النظر في التعليمات البرمجية ، أولا بدون متقلبة الكلمة الأساسية

class MyThread extends Thread {
    private boolean running = true;   //non-volatile keyword

    public void run() {
        while (running) {
            System.out.println("hello");
        }
    }

    public void shutdown() {
        running = false;
    }
}

public class Main {

    public static void main(String[] args) {
        MyThread obj = new MyThread();
        obj.start();

        Scanner input = new Scanner(System.in);
        input.nextLine(); 
        obj.shutdown();   
    }    
}

من الناحية المثالية ، ينبغي لهذا البرنامج print hello حتى يتم الضغط على RETURN key. ولكن على some machines قد يحدث هذا المتغير running هو cached ولا يمكنك تغيير قيمته من طريقة shutdown () التي تؤدي إلى طباعة infinite لنص hello.

وبالتالي باستخدام كلمة رئيسية متقلبة ، فمن guaranteed أن المتغير الخاص بك لن يتم تخزينه مؤقتًا ، أي سوف run fine في all machines.

private volatile boolean running = true;  //volatile keyword

وبالتالي فإن استخدام الكلمة الأساسية المتقلبة هو good و safer programming practice.

10
Prateek Joshi

Variable Volatile : الكلمة الرئيسية المتطايرة قابلة للتطبيق على المتغيرات. تضمن الكلمة الأساسية المتطايرة في Java أن قيمة المتغير المتغير ستتم قراءتها دائمًا من الذاكرة الرئيسية وليس من ذاكرة التخزين المؤقت المحلية لمؤشر الترابط.

Access_Modifier volatile DataType Variable_Name;

حقل متغير: إشارة إلى VM أن مؤشرات ترابط متعددة قد تحاول الوصول إلى/تحديث قيمة الحقل في نفس الوقت. إلى نوع خاص من متغيرات المثيلات التي يجب مشاركتها بين جميع سلاسل العمليات ذات القيمة المعدلة. على غرار المتغير الثابت (Class) ، يتم تخزين نسخة واحدة فقط من القيمة المتقلبة في الذاكرة الرئيسية ، لذا قبل كل عمليات ALU ، يتعين على كل مؤشر ترابط قراءة القيمة المحدّثة من الذاكرة الرئيسية بعد تشغيل ALU عليه الكتابة إلى الذاكرة الرئيسية direclty. (تتم مزامنة الكتابة إلى متغير متغير متغير - مع كل القراءات التالية لـ v بواسطة أي مؤشر ترابط)هذا يعني أن التغييرات التي تطرأ على متغير متغير تكون مرئية دائمًا لمؤشرات الترابط الأخرى.

 enter image description here 

هنا إلى nonvoltaile variable إذا قام مؤشر الترابط t1 بتغيير القيمة في ذاكرة التخزين المؤقت t1 ، لا يمكن لمؤشر الترابط t2 الوصول إلى القيمة التي تم تغييرها حتى يكتب t1 ، t2 يقرأ من الذاكرة الرئيسية للحصول على أحدث قيمة معدلة ، والتي قد تؤدي إلى Data-Inconsistancy.

لا يمكن التخزين المؤقت متقلبة - المجمع

    +--------------+--------+-------------------------------------+
    |  Flag Name   |  Value | Interpretation                      |
    +--------------+--------+-------------------------------------+
    | ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached.|
    +--------------+--------+-------------------------------------+
    |ACC_TRANSIENT | 0x0080 | Declared transient; not written or  |
    |              |        | read by a persistent object manager.|
    +--------------+--------+-------------------------------------+

Shared Variables : تسمى الذاكرة التي يمكن مشاركتها بين مؤشرات الترابط الذاكرة المشتركة أو ذاكرة الكومة. يتم تخزين كافة حقول المثيلات والحقول الثابتة وعناصر الصفيف في ذاكرة الكومة.

التزامن : التزامن قابل للتطبيق على الطرق ، الكتل. يسمح بتنفيذ مؤشر ترابط واحد فقط في كل مرة على الكائن. إذا كان t1 يتحكم ، فيجب على مؤشرات الترابط المتبقية الانتظار حتى تحرير عنصر التحكم.

مثال:

public class VolatileTest implements Runnable {

    private static final int MegaBytes = 10241024;

    private static final Object counterLock = new Object();
    private static int counter = 0;
    private static volatile int counter1 = 0;

    private volatile int counter2 = 0;
    private int counter3 = 0;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            concurrentMethodWrong();
        }

    }

    void addInstanceVolatile() {
        synchronized (counterLock) {
            counter2 = counter2 + 1;
            System.out.println( Thread.currentThread().getName() +"\t\t « InstanceVolatile :: "+ counter2);
        }
    }

    public void concurrentMethodWrong() {
        counter = counter + 1;
        System.out.println( Thread.currentThread().getName() +" « Static :: "+ counter);
        sleepThread( 1/4 );

        counter1 = counter1 + 1;
        System.out.println( Thread.currentThread().getName() +"\t « StaticVolatile :: "+ counter1);
        sleepThread( 1/4 );

        addInstanceVolatile();
        sleepThread( 1/4 );

        counter3 = counter3 + 1;
        sleepThread( 1/4 );
        System.out.println( Thread.currentThread().getName() +"\t\t\t\t\t « Instance :: "+ counter3);
    }
    public static void main(String[] args) throws InterruptedException {
        Runtime runtime = Runtime.getRuntime();

        int availableProcessors = runtime.availableProcessors();
        System.out.println("availableProcessors :: "+availableProcessors);
        System.out.println("MAX JVM will attempt to use : "+ runtime.maxMemory() / MegaBytes );
        System.out.println("JVM totalMemory also equals to initial heap size of JVM : "+ runtime.totalMemory() / MegaBytes );
        System.out.println("Returns the amount of free memory in the JVM : "+ untime.freeMemory() / MegaBytes );
        System.out.println(" ===== ----- ===== ");

        VolatileTest volatileTest = new VolatileTest();
        Thread t1 = new Thread( volatileTest );
        t1.start();

        Thread t2 = new Thread( volatileTest );
        t2.start();

        Thread t3 = new Thread( volatileTest );
        t3.start();

        Thread t4 = new Thread( volatileTest );
        t4.start();

        Thread.sleep( 10 );;

        Thread optimizeation = new Thread() {
            @Override public void run() {
                System.out.println("Thread Start.");

                Integer appendingVal = volatileTest.counter2 + volatileTest.counter2 + volatileTest.counter2;

                System.out.println("End of Thread." + appendingVal);
            }
        };
        optimizeation.start();
    }

    public void sleepThread( long sec ) {
        try {
            Thread.sleep( sec * 1000 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ثابت [Class Field] vs متقلبة [Instance Field] - كلاهما لا يتم تخزينهما مؤقتًا بواسطة سلاسل الرسائل

  • الحقول الثابتة شائعة في جميع مؤشرات الترابط ويتم تخزينها في "طريقة المنطقة". ثابت مع عدم وجود فائدة متقلبة. لا يمكن تسلسل الحقل الثابت.

  • متقلبة تستخدم أساسا مع متغير مثيل التي يتم تخزينها في منطقة كومة الذاكرة المؤقتة. الاستخدام الرئيسي للمتقلبة هو الحفاظ على قيمة محدثة في جميع المواضيع. مثيل الحقل المضطرب يمكن أن يكون مسلسل .

@نرى

6
Yash

عندما يكون المتغير volatile ، فإنه يضمن عدم تخزينه مؤقتًا وأن مؤشرات الترابط المختلفة ستشاهد القيمة المحدّثة. ومع ذلك ، لا يضمن عدم تمييزه volatile عكس ذلك. volatile كان أحد تلك الأشياء التي تم كسرها في JVM لفترة طويلة ولا تزال غير مفهومة جيدًا دائمًا.

3
Jeff Storey

من الناحية المثالية ، إذا لم يكن keepRunning متقلبًا ، فيجب أن يستمر الخيط في العمل إلى أجل غير مسمى. ولكن ، لا يتوقف بعد بضع ثوان.

إذا كنت تعمل في معالج واحد أو إذا كان نظامك مشغولاً للغاية ، فقد يقوم نظام التشغيل بتبديل مؤشرات الترابط التي تسبب بعض مستويات إبطال ذاكرة التخزين المؤقت. كما ذكر الآخرون ، فإن عدم وجود اسم volatile لا يعني أن الذاكرة ستشارك لا لكن JVM تحاول عدم مزامنة الذاكرة إذا كان ذلك ممكنًا لأسباب تتعلق بالأداء وبالتالي قد لا يتم تحديث الذاكرة.

شيء آخر تجدر الإشارة إليه هو أن System.out.println(...) تتم مزامنتها لأن PrintStream الأساسي يقوم بالمزامنة لإيقاف تداخل المخرجات. إذن أنت تحصل على مزامنة الذاكرة "مجانًا" في السلسلة الرئيسية. لا يزال هذا لا يفسر لماذا ترى حلقة القراءة التحديثات على الإطلاق.

سواء كانت خطوط println(...) داخل أو خارج ، فإن البرنامج يدور لي تحت Java6 على MacBook Pro مع Intel i7.

يمكن لأي شخص أن يفسر متقلبة مع مثال؟ ليس مع نظرية من JLS.

أعتقد أن مثالك جيد. لست متأكدًا من السبب في أنها لا تعمل مع إزالة جميع عبارات System.out.println(...). إنه يعمل من أجلي.

هو بديل متقلبة للمزامنة؟ هل يحقق الذرية؟

فيما يتعلق بمزامنة الذاكرة ، يقوم volatile بإزالة نفس حواجز الذاكرة مثل كتلة synchronized فيما عدا أن حاجز volatile أحادي الاتجاه مقابل ثنائي الاتجاه. يقرأ volatile رمي حاجز التحميل بينما يكتب رمي حاجز المتجر. كتلة synchronized هي حاجز ثنائي الاتجاه.

فيما يتعلق بـ atomicity ، فإن الجواب "يعتمد". إذا كنت تقرأ أو تكتب قيمة من حقل ما ، فإن volatile توفر ذرية مناسبة. ومع ذلك ، فإن زيادة حقل volatile تعاني من قيود أن ++ هي في الواقع 3 عمليات: القراءة ، الزيادة ، الكتابة. في هذه الحالة أو حالات mutex الأكثر تعقيدًا ، قد تكون كتلة synchronized الكاملة ضرورية.

2
Gray

volatile لن يخلق بالضرورة تغييرات عملاقة ، اعتمادًا على JVM والمترجم. ومع ذلك ، بالنسبة للعديد من الحالات (الحافة) ، يمكن أن يكون الفرق بين التحسين يسبب فشل تغييرات المتغير في أن يلاحظ بدلاً من كتابتها بشكل صحيح.

بشكل أساسي ، قد يختار المُحسِّن وضع متغيرات غير متقلبة في السجلات أو على الحزمة. إذا غيرهم مؤشر ترابط آخر في كومة الذاكرة المؤقتة أو في بدايات الفئات ، فسيظل مؤشر الترابط الآخر يبحث عنه في المجموعة ، وسيكون غير صالح.

volatile يضمن عدم حدوث مثل هذه التحسينات وجميع القراءات والكتابات تكون مباشرة في كومة الذاكرة المؤقتة أو أي مكان آخر ستشاهدها جميع سلاسل الرسائل.

2
Andrey Akhmetov

الرجاء العثور على الحل أدناه ،

لن يتم تخزين قيمة هذا المتغير مؤقتًا في مؤشر ترابط محليًا: كل القراءات والكتابات ستذهب مباشرة إلى "الذاكرة الرئيسية". قوة متقلبة مؤشر الترابط لتحديث المتغير الأصلي لكل مرة.

public class VolatileDemo {

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {

        ChangeMaker changeMaker = new ChangeMaker();
        changeMaker.start();

        ChangeListener changeListener = new ChangeListener();
        changeListener.start();

    }

    static class ChangeMaker extends Thread {

        @Override
        public void run() {
            while (MY_INT < 5){
                System.out.println("Incrementing MY_INT "+ ++MY_INT);
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException exception) {
                    exception.printStackTrace();
                }
            }
        }
    }

    static class ChangeListener extends Thread {

        int local_value = MY_INT;

        @Override
        public void run() {
            while ( MY_INT < 5){
                if( local_value!= MY_INT){
                    System.out.println("Got Change for MY_INT "+ MY_INT);
                    local_value = MY_INT;
                }
            }
        }
    }

}

يرجى الرجوع إلى هذا الرابط http://Java.dzone.com/articles/Java-volatile-keyword-0 للحصول على مزيد من الوضوح فيه.

1
Azhaguvel A

تخبر الكلمة الأساسية المتقلبة JVM أنه قد يتم تعديلها بواسطة مؤشر ترابط آخر. كل مؤشر ترابط لديه مكدس خاص به ، وبالتالي يمكن الوصول إلى نسخته الخاصة من المتغيرات. عندما يتم إنشاء سلسلة رسائل ، فإنها تنسخ قيمة جميع المتغيرات التي يمكن الوصول إليها في ذاكرتها.

public class VolatileTest {
    private static final Logger LOGGER = MyLoggerFactory.getSimplestLogger();

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {
        new ChangeListener().start();
        new ChangeMaker().start();
    }

    static class ChangeListener extends Thread {
        @Override
        public void run() {
            int local_value = MY_INT;
            while ( local_value < 5){
                if( local_value!= MY_INT){
                    LOGGER.log(Level.INFO,"Got Change for MY_INT : {0}", MY_INT);
                     local_value= MY_INT;
                }
            }
        }
    }

    static class ChangeMaker extends Thread{
        @Override
        public void run() {

            int local_value = MY_INT;
            while (MY_INT <5){
                LOGGER.log(Level.INFO, "Incrementing MY_INT to {0}", local_value+1);
                MY_INT = ++local_value;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }
    }
}

جرب هذا المثال مع وبدون متقلبة.

1
Adarsh Mathew Thomas

عادة ما يتم استخدام الكائنات التي تم إعلانها على أنها متقلبة لتوصيل معلومات الحالة بين مؤشرات الترابط ، لضمان تحديث ذاكرة التخزين المؤقت لوحدة المعالجة المركزية (CPU) ، أي الاحتفاظ بها في حالة تزامن ، مع وجود حقول متقلبة ، أو تعليمات وحدة المعالجة المركزية ، أو حاجز للذاكرة ، يُطلق عليه غالبًا اسم membar أو السياج ، ينبعث لتحديث ذاكرة التخزين المؤقت وحدة المعالجة المركزية مع تغيير في قيمة حقل متقلبة.

يخبر معدّل التقلب المترجم أن المتغير الذي تم تعديله بواسطة متقلّب يمكن أن يتغير بشكل غير متوقع من قبل أجزاء أخرى من البرنامج.

يجب استخدام المتغير المتغير في سياق مؤشر الترابط فقط. انظر المثال هنا

0
user2554822