هل يجب قفل الحروف/المحددات من الأنواع البدائية باستخدام ReadWriteLock في تطبيق متعدد الخيوط؟

StackOverflow https://stackoverflow.com/questions/5014266

سؤال

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

class Example {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private int i;
    private boolean b;

    public int getI() {
      lock.readLock().lock();
      final int tmp = i;
      lock.readLock().unlock(),
      return tmp;
    }

    public void setI( final int i ) {
      lock.writeLock().lock();
      this.i = i;
      lock.writeLock().unlock();
    }

    public boolean getB() {
      lock.readLock().lock();
      final boolean tmp = b;
      lock.readLock().unlock(),
      return tmp;
    }

    public void setB( final boolean b ) {
      lock.writeLock().lock();
      this.b = b;
      lock.writeLock().unlock();
    }
}

من أجل البساطة تركت try...finally كتل حول الأقفال في هذا المثال.

أتساءل عما إذا كان من الضروري (أو دعنا نقول موصى به) قفل/مزامنة الحروف وأدوات ضبط الأنواع البدائية؟أعلم أن عمليات التعيين والإرجاع في Java هي عمليات ذرية.ولكن باستخدام هذه الأقفال، لا أتأكد من أن كل موصل يحصل على أحدث قيمة (تساوي استخدام volatile)?

ماذا لو كان البدائيون double أو long?

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

المحلول

هذا يعتمد.

ومع ذلك، لاحظ أنك تحتاج عادةً إلى مزامنة العمليات على مستوى أكثر دقة، على سبيل المثال، هذا:

Example e = ...;

synchronized (e) {
    e.setI(e.getI() + 10);
}

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

نصائح أخرى

لديك شيء مثل AtomicInteger في Java، والذي يعمل بشكل جيد مع تطبيق MultiThreaded.

اسأل نفسك ما إذا كان من الممكن تنفيذ هذا الفصل كفئة غير قابلة للتغيير.سيكون من الأسهل العمل معه وسيكون آمنًا بطبيعته.لا داعي للقلق بشأن التزامن والأقفال والمزامنة وما إلى ذلك.

مثال على فئة غير قابلة للتغيير:

final class Example {
    private final int i;
    private final boolean b;

    public Example(int i, boolean b){
        this.i = i ;
        this.b = b;
    }

    public int getI() {
        return i;
    }

    public boolean getB() {
        return b;
    }
}

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

على سبيل المثاللنفترض أن لديك نظامًا مكونًا من 32 مركزًا يتوسع بشكل مثالي ويعمل بشكل أسرع بمقدار 32 مرة من نظام أساسي واحد.ومع ذلك، فإن الوصول إلى الحقل بدون قفل يستغرق 1 ns، مع القفل يستغرق 1 us (1000 ns) لذا في النهاية يمكن أن يستغرق تطبيقك حوالي 30x أبطأ.(1000 أبطأ / 32 أسرع) إذا كان لديك 4 نوى فقط، فمن الممكن أن يكون أبطأ بمئات المرات، مما يتعارض مع غرض وجود مؤشرات ترابط متعددة في المقام الأول.IMHO.

ليس من الضروري قفل/مزامنة الحروف وأدوات ضبط الأنواع البدائية - يعد وضع علامات عليها على أنها متقلبة كافيًا في معظم الحالات (بصرف النظر عن الحروف المزدوجة والطويلة كما ذكرت)

كما ذكرت إحدى المنشورات السابقة، يجب أن تكون على دراية بتسلسلات القراءة والتحديث، مثل incrementI(int num) والتي من المحتمل أن تستدعي getI() وsetI() - في هذه الحالة يمكنك إضافة "incrementI(int) المتزامن" num)' إلى فئة المثال الخاصة بك.يتم بعد ذلك إجراء القفل على مستوى أعلى، مما يقلل الحاجة إلى أقفال منفصلة للقراءة والكتابة ويكون سهل الاستخدام حيث تظل البيانات والسلوك معًا.تعتبر هذه التقنية أكثر فائدة إذا كنت تقوم بقراءة/تحديث عدة حقول في وقت واحد.

على الرغم من أنك إذا كنت تقوم ببساطة بقراءة/كتابة/تحديث حقل واحد في كل مرة، فإن فئات AtomicXX تكون أكثر ملاءمة

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

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