في الآونة الأخيرة ، واجهت بعض سيناريوهات التزامن العالي في العمل تتطلب القفل لضمان صحة منطق العمل ، ويجب ألا يتأثر الأداء بعد القفل كثيرًا. تتمثل الفكرة الأولية في قفل البيانات من خلال الكلمات الرئيسية مثل الطوابع الزمنية والمعرفات ، وبالتالي ضمان تزامن أنواع مختلفة من معالجة البيانات. إن الحبيبات التي توفرها API الخاصة بـ Java كبيرة جدًا ، ومن الصعب تلبية هذه الاحتياجات في نفس الوقت ، لذلك كتبت عدة ملحقات بسيطة بنفسي ...
1. قفل القطاع
بالاعتماد على فكرة تجزئة ConcurrentHashMap ، يتم إنشاء عدد معين من الأقفال ، وعند استخدامه ، يتم إرجاع القفل المقابل وفقًا للمفتاح. هذا هو أبسط وأعلى أداء ، وفي النهاية تم تبني استراتيجية القفل بين العديد من التطبيقات. الرمز كما يلي:
/*** قفل القطاع ، يوفر النظام عددًا معينًا من الأقفال الأصلية ، والحصول على القفل المقابل بناءً على قيمة التجزئة للكائن الوارد وأضف القفل* ملاحظة: إذا كانت قيمة التجزئة للكائن لتغييرات مقفلة ، فقد يتسبب ذلك في عدم إمكانية إطلاق القفل بنجاح !!! */SPERSION SPERSITINCH <T> {قطاعات Integer الخاصة = 16 ؛ // العدد الافتراضي للقطاعات الخاصة HASHMAP النهائي <integer ، reentrantlock> lockmap = new hashmap <> () ؛ segmentlock () {init (null ، false) ؛ } segmentlock (عدد صحيح ، معرض منطقي) {init (counts ، fair) ؛ } private void init (عدد صحيح ، معرض منطقي) {if (counts! = null) {secments = counts ؛ } لـ (int i = 0 ؛ i <secments ؛ i ++) {lockmap.put (i ، new reentrantlock (fair)) ؛ }} قفل void public (مفتاح t) {reentrantlock lock = lockmap.get ((key.hashCode () >>> 1) ٪) ؛ lock.lock () ؛ } public void inlock (t key) {reentrantlock lock = lockmap.get ((key.hashCode () >>> 1) ٪) ؛ lock.unlock () ؛ }}2. قفل التجزئة
تتمثل استراتيجية القفل الثانية التي تم تطويرها استنادًا إلى القفل المقطوع أعلاه في تحقيق قفل حقيقي للحبيبات. يمكن لكل كائن ذي قيمة تجزئة مختلفة الحصول على قفل مستقل خاص به. في الاختبار ، عند تنفيذ الرمز المقفل بسرعة ، تكون الكفاءة أبطأ بنسبة 30 ٪ من قفل القطاع. إذا كانت هناك عملية طويلة تستغرق وقتًا طويلاً ، فيجب أن يكون الشعور بالأداء أفضل. الرمز كما يلي:
الطبقة العامة hashlock <T> {private boolean isfair = false ؛ القطاع النهائي الخاص <T> segmentlock = جديد SPISTINCLOCK <> () ؛ // قفل القطاع الخاص النهائي الخاص concurrenthashmap <t ، lockinfo> lockmap = concurrenthashmap new concurrenthashmap <> () ؛ Hashlock () {} hashlock (معرض منطقي) {isfair = fair ؛ } قفل باطل عام (مفتاح t) {lockinfo lockinfo ؛ spessionlock.lock (مفتاح) ؛ حاول {lockinfo = lockmap.get (مفتاح) ؛ if (lockinfo == null) {lockinfo = new lockinfo (isFair) ؛ lockmap.put (مفتاح ، lockinfo) ؛ } آخر {lockinfo.count.incrementandget () ؛ }} أخيرًا {summentlock.unlock (مفتاح) ؛ } lockinfo.lock.lock () ؛ } public void inlock (t key) {lockinfo lockinfo = lockmap.get (key) ؛ if (lockinfo.count.get () == 1) {segmentlock.lock (key) ؛ حاول {if (lockinfo.count.get () == 1) {lockmap.remove (key) ؛ }} أخيرًا {summentlock.unlock (مفتاح) ؛ }} lockinfo.count.decrementandget () ؛ lockinfo.unlock () ؛ } فئة ثابتة خاصة Lockinfo {public reentrantlock lock ؛ عدد Atomicinteger العام = New AtomicInteger (1) ؛ LOCKINFO الخاص (معرض منطقي) {this.lock = new reentrantlock (fair) ؛ } public void lock () {this.lock.lock () ؛ } public void inlock () {this.lock.unlock () ؛ }}}3. قفل مرجعي ضعيف
تكون أقفال التجزئة معيبًا دائمًا لأن الأقفال المجزأة يتم تقديمها لضمان مزامنة إنشاء القفل والتدمير ، لذلك تمت كتابة قفل ثالث لطلب أداء أفضل وأقفال حبيبة أدق. تتمثل فكرة هذا القفل في إنشاء قفل بمساعدة المراجع الضعيفة في Java ، وتسليم تدمير القفل إلى مجموعة القمامة JVM لتجنب استهلاك إضافي.
من المؤسف بعض الشيء أنه نظرًا لاستخدام ConcurrenthashMap كحاوية قفل ، فمن غير قادر على التخلص حقًا من الأقفال المجزأة. أداء هذا القفل أسرع بنسبة 10 ٪ من أداء هاشلوك. رمز القفل:
/*** قفل مرجعي ضعيف ، وتوفير وظيفة قفل مستقلة لكل تجزئة مستقلة*/فئة عامة prefhashlock <T> {private concurrenthashmap <t ، deplockRef <t ، reentrantlock >> lockmap = concurrenthashmap <> () الجديد ؛ QuipeSeue الخاص <reentrantlock> Queue = new ReferenceQueue <> () ؛ Public REENTRANTLOCK GET (T KEY) {if (lockmap.size ()> 1000) {clearemptyref () ؛ } PreferReference <SeentRantlock> lockRef = lockmap.get (مفتاح) ؛ reentrantlock lock = (lockref == null؟ null: lockref.get ()) ؛ بينما (lock == null) {lockmap.putifabsent (المفتاح ، الضعيف الجديد <> (جديد reentrantlock () ، قائمة الانتظار ، المفتاح)) ؛ lockref = lockmap.get (مفتاح) ؛ lock = (lockref == null؟ null: lockref.get ()) ؛ if (lock! = null) {return lock ؛ } clearemptyref () ؛ } قفل الإرجاع ؛ } suppressWarnings ("unchected") private void clearemptyref () {Reference <؟ يمتد reentrantlock> المرجع ؛ بينما ((ref = queue.poll ())! = null) {preflockRef <t ،؟ يمتد reentrantlock> deplockRef = (DeplockRef <t ،؟ يمتد reentrantlock>) المرجع ؛ lockmap.remove (preflockref.key) ؛ }} private static static class premockref <t ، k> يمتد الضعف <k> {final t key ؛ Private DeplockRef (k Reference ، ReferenceQueue <؟ super k> q ، t key) {super (Referent ، Q) ؛ this.key = المفتاح ؛ }}}PostScript
في البداية أردت استخدام Locksupport و AQS لتنفيذ أقفال الحبيبات الدقيقة. كما كتبت ، وجدت أن الأشياء التي كنت أقوم بتنفيذها لم تكن مختلفة كثيرًا عن أقفال جافا الأصلية ، لذلك تخليت عن تغليف أقفال Java الخاصة ، التي تضيع الكثير من الوقت.
في الواقع ، بعد تنفيذ هذه الأقفال ذات الحبيبات الدقيقة ، لدينا أفكار جديدة ، مثل إرسال البيانات إلى مؤشرات ترابط خاصة من خلال أفكار التجزئة ، والتي يمكن أن تقلل من وقت الحظر لعدد كبير من الخيوط وتركها في الاستكشاف المستقبلي ...