سؤال

في العمل اليوم، صادفت volatile الكلمة الأساسية في جافا.ولم أكن على دراية بها، وجدت هذا التفسير:

نظرية وممارسة جافا:إدارة التقلبات

بالنظر إلى التفاصيل التي تشرح بها هذه المقالة الكلمة الرئيسية المعنية، هل سبق لك استخدامها أو هل يمكن أن ترى حالة يمكنك فيها استخدام هذه الكلمة الرئيسية بالطريقة الصحيحة؟

هل كانت مفيدة؟

المحلول

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

للإجابة على سؤالك:نعم أستخدم أ volatile متغير للتحكم فيما إذا كانت بعض التعليمات البرمجية تستمر في حلقة.الحلقة تختبر volatile القيمة وتستمر إذا كان الأمر كذلك true.يمكن ضبط الشرط على false عن طريق استدعاء طريقة "التوقف".ترى الحلقة false وينتهي عندما يختبر القيمة بعد اكتمال تنفيذ طريقة الإيقاف.

الكتاب "جافا التزامن في الممارسة العملية"، الذي أوصي به بشدة، يقدم شرحًا جيدًا لـ volatile.هذا الكتاب كتبه نفس الشخص الذي كتب مقالة IBM المشار إليها في السؤال (في الواقع، يستشهد بكتابه في أسفل تلك المقالة).استخدامي ل volatile هو ما تسميه مقالته "علامة حالة النمط 1".

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

نصائح أخرى

"... يضمن المُعدِّل المتطاير أن أي مؤشر ترابط يقرأ حقلاً ما سيرى أحدث قيمة مكتوبة." - جوش بلوخ

إذا كنت تفكر في استخدام volatile, ، اقرأ على الحزمة java.util.concurrent الذي يتعامل مع السلوك الذري.

منشور ويكيبيديا على أ نمط سينجلتون يظهر متقلبة في الاستخدام.

نقطة مهمة بخصوص volatile:

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

مصدر

مثال لاستخدام volatile:

public class Singleton {
    private static volatile Singleton _instance; // volatile variable
    public static Singleton getInstance() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

نحن نقوم بإنشاء مثيل بتكاسل في الوقت الذي يأتي فيه الطلب الأول.

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

لماذا يحدث هذا؟لأن سلاسل القارئ لا تقوم بأي قفل وحتى يخرج خيط الكاتب من كتلة متزامنة، لن تتم مزامنة الذاكرة وقيمة _instance لن يتم تحديثه في الذاكرة الرئيسية.باستخدام الكلمة الأساسية Volatile في Java، تتم معالجة ذلك بواسطة Java نفسها وستكون هذه التحديثات مرئية لجميع سلاسل رسائل القارئ.

خاتمة: volatile يتم استخدام الكلمة الأساسية أيضًا لتوصيل محتوى الذاكرة بين سلاسل الرسائل.

مثال على استخدام بدون متقلبة:

public class Singleton{    
    private static Singleton _instance;   //without volatile variable
    public static Singleton getInstance(){   
          if(_instance == null){  
              synchronized(Singleton.class){  
               if(_instance == null) _instance = new Singleton(); 
      } 
     }   
    return _instance;  
    }

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

enter image description here

volatile الاستخدام في جافا:

التكرارات سريعة الفشل هي عادة تم تنفيذها باستخدام أ volatile العداد على كائن القائمة.

  • عند تحديث القائمة، يتم زيادة العداد.
  • عندما Iterator يتم إنشاء القيمة الحالية للعداد في ملف Iterator هدف.
  • عندما Iterator بعد تنفيذ العملية، تقوم الطريقة بمقارنة قيمتي العداد وطرح a ConcurrentModificationException إذا كانوا مختلفين.

عادةً ما يكون تنفيذ التكرارات الآمنة من الفشل خفيف الوزن.وهي تعتمد عادةً على خصائص هياكل بيانات تنفيذ القائمة المحددة.لا يوجد نمط عام.

المتقلبة مفيدة جدا لوقف المواضيع.

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

النمط الذي أستخدمه للخيوط هو:

public class Foo extends Thread {
  private volatile boolean close = false;
  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

لاحظ كيف ليست هناك حاجة للمزامنة

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

تم الإعلان عن متغير بـ volatile الكلمة الرئيسية لها صفتان رئيسيتان تجعلها مميزة.

  1. إذا كان لدينا متغير متطاير، فلا يمكن تخزينه مؤقتًا في ذاكرة التخزين المؤقت للكمبيوتر (المعالج الدقيق) بواسطة أي مؤشر ترابط.يتم الوصول دائمًا من الذاكرة الرئيسية.

  2. إذا كان هناك عملية الكتابة يحدث في متغير متقلب، وفجأة أ قراءة العملية يُطلب، ويضمن أن سيتم الانتهاء من عملية الكتابة قبل عملية القراءة.

والصفتان المذكورتان يستنتجان ذلك

  • جميع المواضيع التي تقرأ متغيرًا متقلبًا ستقرأ بالتأكيد القيمة الأحدث.لأنه لا توجد قيمة مخزنة مؤقتًا يمكنها تلويثها.وأيضًا لن يتم منح طلب القراءة إلا بعد الانتهاء من عملية الكتابة الحالية.

ومن ناحية أخرى،

  • إذا قمنا بمزيد من التحقيق في #2 التي ذكرتها، يمكننا أن نرى ذلك volatile تعد الكلمة الأساسية طريقة مثالية للحفاظ على متغير مشترك له عدد 'n' من سلاسل الرسائل المقروءة وموضوع كتابة واحد فقط للوصول إليه.بمجرد أن نضيف volatile الكلمة الأساسية، لقد تم ذلك.لا يوجد أي حمل آخر حول سلامة الخيط.

على العكس من ذلك،

نحن لا أستطيع الاستفادة من volatile الكلمة الأساسية فقط، لتلبية المتغير المشترك الذي لديه أكثر من كتابة المواضيع الوصول إليه.

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

ولم يذكر أحد معالجة عملية القراءة والكتابة للنوع المتغير الطويل والمزدوج.القراءة والكتابة هي عمليات ذرية للمتغيرات المرجعية ولمعظم المتغيرات البدائية، باستثناء أنواع المتغيرات الطويلة والمزدوجة، والتي يجب أن تستخدم الكلمة الأساسية المتقلبة لتكون عمليات ذرية. @وصلة

في رأيي، هناك سيناريوهان مهمان بخلاف إيقاف سلسلة المحادثات التي يتم فيها استخدام الكلمة الرئيسية المتقلبة، وهما:

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

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

إذا كنت تقوم بتطوير تطبيق سيتم نشره على خادم التطبيقات (Tomcat، وJBoss AS، وGlassfish، وما إلى ذلك) فلن يتعين عليك التعامل مع التحكم في التزامن بنفسك لأنه تمت معالجته بالفعل بواسطة خادم التطبيقات.في الواقع، إذا كنت أتذكر بشكل صحيح أن معيار Java EE يحظر أي تحكم في التزامن في servlets وEJBs، نظرًا لأنه جزء من طبقة "البنية التحتية" التي من المفترض أن يتم تحريرها من التعامل معها.لا يمكنك التحكم في التزامن في هذا التطبيق إلا إذا كنت تقوم بتنفيذ كائنات مفردة.تم تناول هذا بالفعل إذا قمت بربط مكوناتك باستخدام إطار مثل Spring.

لذلك، في معظم حالات تطوير Java حيث يكون التطبيق عبارة عن تطبيق ويب ويستخدم إطار عمل IoC مثل Spring أو EJB، لن تحتاج إلى استخدام "متغير".

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

package io.netty.example.telnet;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static volatile  int a = 0;
    public static void main(String args[]) throws InterruptedException{

        List<Thread> list = new  ArrayList<Thread>();
        for(int i = 0 ; i<11 ;i++){
            list.add(new Pojo());
        }

        for (Thread thread : list) {
            thread.start();
        }

        Thread.sleep(20000);
        System.out.println(a);
    }
}
class Pojo extends Thread{
    int a = 10001;
    public void run() {
        while(a-->0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Main.a++;
            System.out.println("a = "+Main.a);
        }
    }
}

حتى لو قمت بوضع نتائج متقلبة أو لا ستختلف دائمًا.ولكن إذا كنت تستخدم AtomicInteger على النحو التالي، فستكون النتائج هي نفسها دائمًا.وهذا هو نفسه مع متزامنة أيضا.

    package io.netty.example.telnet;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;

    public class Main {

        public static volatile  AtomicInteger a = new AtomicInteger(0);
        public static void main(String args[]) throws InterruptedException{

            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }

            for (Thread thread : list) {
                thread.start();
            }

            Thread.sleep(20000);
            System.out.println(a.get());

        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a.incrementAndGet();
                System.out.println("a = "+Main.a);
            }
        }
    }

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

  1. يجب عليك استخدام متقلبة فقط إذا فهمت تمامًا ما تفعله وكيف يختلف لمزامنته.في العديد من المواقف ، يبدو أن المتقلبة ، على السطح ، بديلاً أكثر بساطة للأداء المتزامن ، عندما يوضح فهم أفضل للمتقلبة في كثير من الأحيان أن المتزامن هو الخيار الوحيد الذي قد يعمل.
  2. التقلب لا يعمل بالفعل في الكثير من JVMs الأقدم ، على الرغم من أن المزامنة لا.أتذكر أنني رأيت مستندًا يشير إلى مستويات الدعم المختلفة في أجهزة JVM المختلفة ولكن لسوء الحظ لا يمكنني العثور عليه الآن.تأكد من ذلك إذا كنت تستخدم Java pre 1.5 أو إذا لم يكن لديك التحكم في JVMs التي سيتم تشغيل برنامجك عليها.

بكل تأكيد نعم.(وليس فقط في Java، ولكن أيضًا في C#.) هناك أوقات تحتاج فيها إلى الحصول على أو تعيين قيمة مضمونة لتكون عملية ذرية على النظام الأساسي الخاص بك، على سبيل المثال، int أو boolean، ولكن لا تتطلب ذلك النفقات العامة لقفل الموضوع.تسمح لك الكلمة الأساسية المتقلبة بالتأكد من حصولك على القيمة عندما تقرأها حاضِر القيمة وليست قيمة مخزنة مؤقتًا أصبحت قديمة بسبب الكتابة على مؤشر ترابط آخر.

سيقرأ كل مؤشر ترابط يصل إلى حقل متطاير قيمته الحالية قبل المتابعة، بدلاً من (المحتمل) استخدام قيمة مخبأة.

متغير العضو فقط يمكن أن يكون متقلبًا أو عابرًا.

هناك نوعان من الاستخدامات المختلفة للكلمة الرئيسية المتقلبة.

  1. يمنع JVM من قراءة القيم من السجل (باعتباره ذاكرة تخزين مؤقت)، ويفرض قراءة قيمته من الذاكرة.
  2. يقلل من خطر حدوث أخطاء في عدم تناسق الذاكرة.

يمنع JVM من القراءة قيم في السجل ، ويجبر قيمته على القراءة من الذاكرة.

أ علم مشغول يُستخدم لمنع سلسلة المحادثات من الاستمرار عندما يكون الجهاز مشغولاً والعلامة غير محمية بقفل:

while (busy) {
    /* do something else */
}

سيستمر مؤشر ترابط الاختبار عندما يقوم مؤشر ترابط آخر بإيقاف تشغيل علم مشغول:

busy = 0;

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

يقلل من خطر حدوث أخطاء في تناسق الذاكرة.

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

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

الفعل الذري هو الذي يحدث فعليًا مرة واحدة.لا يمكن للفعل الذري أن يتوقف في المنتصف:فإما أن يحدث بشكل كامل، أو لا يحدث على الإطلاق.لا تظهر أي آثار جانبية للعمل الذري حتى اكتمال الإجراء.

فيما يلي الإجراءات التي يمكنك تحديدها على أنها ذرية:

  • القراءات والكتابة هي ذرية للمتغيرات المرجعية وبالنسبة لمعظم المتغيرات البدائية (جميع الأنواع باستثناء طويلة ومزدوجة).
  • عمليات القراءة والكتابة ذرية لجميع المتغيرات المعلنة متقلب(بما في ذلك المتغيرات الطويلة والمزدوجة).

هتافات!

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

المتقلبة لا تتبع.

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

يُظهر اتباع التعليمات البرمجية قيد التشغيل بالكامل كيف يمكن تنفيذ عدد من سلاسل العمليات بترتيب محدد مسبقًا وطباعة المخرجات دون استخدام كلمة رئيسية متزامنة.

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

لتحقيق ذلك، قد نستخدم كود التشغيل الكامل التالي.

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

يحتوي رابط جيثب التالي على ملف تمهيدي، والذي يقدم شرحًا مناسبًا.https://github.com/sankar4git/volatile_thread_ordering

من وثائق أوراكل صفحة, ، تظهر الحاجة إلى متغير متغير لإصلاح مشكلات تناسق الذاكرة:

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

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

كما هو موضح في Peter Parker الجواب في غياب volatile المعدل، قد يكون لكل مكدس مؤشر ترابط نسخته الخاصة من المتغير.وذلك بجعل المتغير as volatile, تم إصلاح مشكلات تناسق الذاكرة.

القي نظرة على جينكوف صفحة تعليمية لفهم أفضل.

قم بإلقاء نظرة على سؤال SE ذي الصلة للحصول على مزيد من التفاصيل حول الحالات المتقلبة وحالات الاستخدام لاستخدام الحالات المتقلبة:

الفرق بين المتطايرة والمتزامنة في جافا

حالة استخدام عملية واحدة:

لديك العديد من المواضيع التي تحتاج إلى طباعة الوقت الحالي بتنسيق معين على سبيل المثال: java.text.SimpleDateFormat("HH-mm-ss").يمكن أن يحتوي Yon على فئة واحدة، والتي تحول الوقت الحالي إلى SimpleDateFormat وتحديث المتغير لكل ثانية واحدة.يمكن لجميع المواضيع الأخرى ببساطة استخدام هذا المتغير المتقلب لطباعة الوقت الحالي في ملفات السجل.

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

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

كمرجع، راجع هذا http://techno-terminal.blogspot.in/2015/11/what-are-volatile-variables.html

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

إذا تم إجراء التغييرات بواسطة خيط واحد وكان الآخرون بحاجة فقط إلى قراءة هذه القيمة، فسيكون المتقلب مناسبًا.

انا يعجبني تفسير جينكوف:

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

في الواقع ، نظرًا لأن Java 5 يضمن الكلمة الرئيسية المتطايرة أكثر من مجرد كتابة المتغيرات المتطايرة إلى الذاكرة الرئيسية والقراءة.

إنه ضمان رؤية ممتد وهو ما يسمى بالضمان قبل حدوثه.

اعتبارات الأداء المتقلبة

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

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

يوجد أدناه رمز بسيط جدًا لتوضيح متطلبات volatile للمتغير الذي يستخدم للتحكم في تنفيذ مؤشر الترابط من مؤشر ترابط آخر (هذا هو أحد السيناريوهات حيث volatile مطلوب).

// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
    public static void main(String[] a) throws Exception {
        Task task = new Task();
        new Thread(task).start();

        Thread.sleep(500);
        long stoppedOn = System.nanoTime();

        task.stop(); // -----> do this to stop the thread

        System.out.println("Stopping on: " + stoppedOn);
    }
}

class Task implements Runnable {
    // Try running with and without 'volatile' here
    private volatile boolean state = true;
    private int i = 0;

    public void stop() {
        state = false;
    } 

    @Override
    public void run() {
        while(state) {
            i++;
        }
        System.out.println(i + "> Stopped on: " + System.nanoTime());
    }
}

متى volatile هو ليس مستخدما: لن ترى أبدا"توقفت عند:xxx"رسالة ولو بعد"التوقف على:xxx"، ويستمر البرنامج في العمل.

Stopping on: 1895303906650500

متى volatile مستخدم: سترى "توقفت عند:xxx' في الحال.

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

العرض التوضيحي: https://repl.it/repls/SilverAgonizingObjectcode

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top