Penjelasan mendetail tentang kunci baca-tulis ReentrantReadWriteLock



1. Pengantar kunci baca-tulis

Pada kenyataannya, ada skenario di mana ada operasi baca dan tulis pada sumber daya bersama, dan operasi tulis tidak sesering operasi baca. Jika tidak ada operasi tulis, tidak ada masalah dengan beberapa utas membaca sumber daya secara bersamaan, jadi beberapa utas harus diizinkan untuk membaca sumber daya bersama pada saat yang sama; tetapi jika utas ingin menulis sumber daya bersama ini, utas lain tidak boleh diizinkan untuk sumber daya itu operasi baca dan tulis sedang berlangsung.

 Untuk skenario ini, paket serentak JAVA menyediakan kunci baca-tulis ReentrantReadWriteLock, yang mewakili dua kunci, satu adalah kunci yang terkait dengan operasi baca, yang disebut kunci bersama; yang lainnya adalah kunci yang terkait dengan penulisan, yang disebut kunci eksklusif , yang dijelaskan sebagai berikut:

Prasyarat utas untuk memasuki kunci baca:

Tidak ada kunci tulis untuk utas lainnya,

Tidak ada permintaan tulis atau permintaan tulis, tetapi thread pemanggil dan thread yang memegang kunci adalah sama.

Prasyarat utas untuk memasuki kunci tulis:

Tidak ada kunci baca untuk utas lainnya

Tidak ada kunci tulis untuk utas lainnya

Kunci baca-tulis memiliki tiga karakteristik penting berikut:

(1) Selektifitas yang adil: Mendukung metode akuisisi yang tidak adil (default) dan fair lock, dan throughput masih tidak adil lebih baik daripada adil.

(2) Re-entry: Baik membaca kunci dan menulis ulang thread dukungan kunci masuk kembali.

(3) Degradasi kunci: Mengikuti urutan memperoleh kunci tulis, memperoleh kunci baca dan kemudian melepaskan kunci tulis, kunci tulis dapat diturunkan menjadi kunci baca.

Kedua, interpretasi kode sumber

Pertama mari kita lihat struktur keseluruhan kelas ReentrantReadWriteLock:

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { /** 读锁 */ private final ReentrantReadWriteLock.ReadLock readerLock; /** 写锁 */ private final ReentrantReadWriteLock.WriteLock writerLock; final Sync sync;  /** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */ public ReentrantReadWriteLock() {  this(false); } /** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */ public ReentrantReadWriteLock(boolean fair) {  sync = fair ? new FairSync() : new NonfairSync();  readerLock = new ReadLock(this);  writerLock = new WriteLock(this); } /** 返回用于写入操作的锁 */ public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }  /** 返回用于读取操作的锁 */ public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; } abstract static class Sync extends AbstractQueuedSynchronizer {} static final class NonfairSync extends Sync {} static final class FairSync extends Sync {} public static class ReadLock implements Lock, java.io.Serializable {} public static class WriteLock implements Lock, java.io.Serializable {}}

1, hubungan warisan kelas

public class ReentrantReadWriteLock  implements ReadWriteLock, java.io.Serializable {}

Catatan: Seperti yang Anda lihat, ReentrantReadWriteLock mengimplementasikan antarmuka ReadWriteLock. Antarmuka ReadWriteLock menentukan spesifikasi untuk memperoleh kunci baca dan kunci tulis, yang perlu diimplementasikan oleh kelas implementasi tertentu. Pada saat yang sama, ia juga mengimplementasikan antarmuka Serializable, yang berarti dapat diserialkan.Dalam kode sumber Anda dapat melihat bahwa ReentrantReadWriteLock mengimplementasikan logika serialisasi sendiri.

2. Kelas batin dari kelas

ReentrantReadWriteLock memiliki lima kelas internal, dan lima kelas internal juga terkait satu sama lain. Hubungan antar kelas internal ditunjukkan pada gambar di bawah ini.

mengajukan
Deskripsi: Seperti yang ditunjukkan pada gambar di atas, Sync mewarisi dari AQS, NonfairSync mewarisi dari kelas Sync, FairSync mewarisi dari kelas Sync (nilai boolean yang diteruskan melalui konstruktor menentukan instance Sync mana yang akan dibuat); ReadLock mengimplementasikan antarmuka Lock dan WriteLock juga Menerapkan antarmuka Lock.

Sinkronisasi kelas:

(1) Hubungan warisan kelas

abstract static class Sync extends AbstractQueuedSynchronizer {}

Deskripsi: Kelas abstrak Sync mewarisi dari kelas abstrak AQS, dan kelas Sync menyediakan dukungan untuk ReentrantReadWriteLock.

(2) Kelas dalam dari kelas

Ada dua kelas internal di dalam kelas Sync, yaitu HoldCounter dan ThreadLocalHoldCounter. HoldCounter terutama digunakan dengan kunci baca. Kode sumber HoldCounter adalah sebagai berikut.

// 计数器static final class HoldCounter { // 计数 int count = 0; // Use id, not reference, to avoid garbage retention // 获取当前线程的TID属性的值 final long tid = getThreadId(Thread.currentThread());}

Deskripsi: HoldCounter memiliki dua atribut utama, count dan tid, di mana count mewakili berapa kali thread pembaca masuk kembali, dan tid mewakili nilai bidang tid dari thread tersebut, yang dapat digunakan untuk mengidentifikasi utas secara unik. Kode sumber ThreadLocalHoldCounter adalah sebagai berikut

// 本地线程计数器static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { // 重写初始化方法,在没有进行set的情况下,获取的都是该HoldCounter值 public HoldCounter initialValue() {  return new HoldCounter(); }}

Deskripsi: ThreadLocalHoldCounter menulis ulang metode initialValue dari ThreadLocal. Kelas ThreadLocal dapat mengaitkan utas dengan objek. Jika tidak ada set, semua get adalah objek HolderCounter yang dibuat dalam metode initialValue.

(3) Atribut kelas

abstract static class Sync extends AbstractQueuedSynchronizer { // 版本序列号 private static final long serialVersionUID = 6317671515068378041L;   // 高16位为读锁,低16位为写锁 static final int SHARED_SHIFT = 16; // 读锁单位 static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 读锁最大数量 static final int MAX_COUNT  = (1 << SHARED_SHIFT) - 1; // 写锁最大数量 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 本地线程计数器 private transient ThreadLocalHoldCounter readHolds; // 缓存的计数器 private transient HoldCounter cachedHoldCounter; // 第一个读线程 private transient Thread firstReader = null; // 第一个读线程的计数 private transient int firstReaderHoldCount;}

Deskripsi: Atribut ini mencakup jumlah maksimum utas kunci baca dan kunci tulis. Penghitung benang lokal, dll.

(4) Pembina kelas

// 构造函数Sync() { // 本地线程计数器 readHolds = new ThreadLocalHoldCounter(); // 设置AQS的状态 setState(getState()); // ensures visibility of readHolds}

Deskripsi: Penghitung utas lokal dan status status AQS disetel di konstruktor Sinkronisasi.

3. Desain status baca dan tulis

Dalam penerapan kunci reentrant, status sinkronisasi menunjukkan berapa kali ia berulang kali diperoleh oleh utas yang sama, yaitu, variabel bilangan bulat dipertahankan, tetapi variabel sebelumnya hanya menunjukkan apakah ia dikunci, dan tidak membedakan antara membaca kunci dan menulis kunci. Kunci baca-tulis perlu mempertahankan status beberapa utas pembaca dan satu utas penulis dalam status sinkronisasi (variabel bilangan bulat).

Realisasi status sinkronisasi dari kunci baca-tulis adalah dengan menggunakan "pemotongan bitwise" pada variabel bilangan bulat: variabel dipotong menjadi dua bagian, 16 bit atas mewakili baca, dan 16 bit bawah mewakili tulis.

mengajukan
Dengan asumsi bahwa nilai status sinkronisasi saat ini adalah S, operasi get dan set adalah sebagai berikut:

(1) Dapatkan status tulis:

S & 0x0000FFFF: Hapus semua 16 bit tinggi

(2) Dapatkan status baca:

S >>> 16: komplemen 0 tanpa tanda, geser 16 bit ke kanan

(3) Tulis status plus 1:

S + 1

(4) Status membaca ditambah 1:

  S + (1 << 16) berarti S + 0x00010000

Dalam penilaian lapisan kode, jika S tidak sama dengan 0, ketika status tulis (S & 0x0000FFFF) dan status baca (S >>> 16) lebih besar dari 0, itu berarti kunci baca baca-tulis kunci telah diperoleh.

4. Akuisisi dan melepaskan kunci tulis

Lihatlah metode kunci dan buka kunci di kelas WriteLock:

public void lock() { sync.acquire(1);}public void unlock() { sync.release(1);}

Dapat dilihat bahwa ini adalah perolehan dan pelepasan status sinkronisasi eksklusif dari panggilan tersebut, sehingga implementasi sebenarnya adalah tryAcquire dan tryRelease of Sync.

Untuk akuisisi kunci tulis, lihat tryAcquire:

 1 protected final boolean tryAcquire(int acquires) { 2  //当前线程 3  Thread current = Thread.currentThread(); 4  //获取状态 5  int c = getState(); 6  //写线程数量(即获取独占锁的重入数) 7  int w = exclusiveCount(c); 8  9  //当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁10  if (c != 0) {11   // 当前state不为0,此时:如果写锁状态为0说明读锁此时被占用返回false;12   // 如果写锁状态不为0且写锁没有被当前线程持有返回false13   if (w == 0 || current != getExclusiveOwnerThread())14    return false;15   16   //判断同一线程获取写锁是否超过最大次数(65535),支持可重入17   if (w + exclusiveCount(acquires) > MAX_COUNT)18    throw new Error("Maximum lock count exceeded");19   //更新状态20   //此时当前线程已持有写锁,现在是重入,所以只需要修改锁的数量即可。21   setState(c + acquires);22   return true;23  }24  25  //到这里说明此时c=0,读锁和写锁都没有被获取26  //writerShouldBlock表示是否阻塞27  if (writerShouldBlock() ||28   !compareAndSetState(c, c + acquires))29   return false;30  31  //设置锁为当前线程所有32  setExclusiveOwnerThread(current);33  return true;34 }

Metode eksklusifCount mewakili jumlah utas yang menempati kunci tulis. Kode sumbernya adalah sebagai berikut:

static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

Penjelasan: Secara langsung DAN state state dan (2 ^ 16-1) yang ekuivalen dengan modulo 2 ^ 16. Jumlah kunci tulis diwakili oleh enam belas bit status rendah.

Seperti yang dapat dilihat dari kode sumbernya, langkah-langkah untuk mendapatkan kunci tulis adalah sebagai berikut:

(1) Pertama, dapatkan c dan w. c mewakili status kunci saat ini; w mewakili jumlah utas tulis. Kemudian tentukan apakah status status sinkronisasi adalah 0. Jika status! = 0, itu berarti utas lain telah memperoleh kunci baca atau kunci tulis, dan jalankan (2); jika tidak, jalankan (5).

(2) Jika status kunci bukan nol (c! = 0), dan status kunci tulis adalah 0 (w = 0), itu berarti bahwa kunci baca ditempati oleh utas lain saat ini, jadi utas saat ini tidak bisa dapatkan kunci tulis, dan secara alami mengembalikan false. Atau status kunci bukan nol, dan status kunci tulis bukan 0, tetapi utas yang memperoleh kunci tulis bukanlah utas saat ini, dan utas saat ini juga tidak dapat memperoleh kunci tulis.

(3) Tentukan apakah utas saat ini telah memperoleh kunci tulis lebih dari jumlah maksimum. Jika melebihi jumlah maksimum, buat pengecualian, jika tidak perbarui status sinkronisasi (saat ini utas saat ini telah memperoleh kunci tulis mengunci dan pembaruan aman untuk thread), dan mengembalikan nilai true.

(4) Jika statusnya 0, baik kunci baca maupun kunci tulis tidak diperoleh saat ini, dan putuskan apakah perlu diblokir (metode yang adil dan tidak adil diterapkan secara berbeda). Di bawah strategi yang tidak adil, itu akan selalu tidak diblokir. Di bawah strategi yang adil Ini akan menilai (menilai apakah ada utas dengan waktu tunggu lebih lama dalam antrian sinkronisasi, jika ada, perlu diblokir, jika tidak, tidak perlu diblokir), jika itu tidak perlu diblokir, CAS memperbarui status sinkronisasi, jika CAS berhasil, itu mengembalikan true, dan gagal Itu berarti kunci direnggut oleh utas lain, dan false dikembalikan. Itu juga mengembalikan false jika perlu diblokir.

(5) Setelah berhasil memperoleh kunci tulis, setel utas saat ini sebagai utas yang menahan kunci tulis dan kembalikan nilai true.

Diagram alir metode adalah sebagai berikut:

mengajukan
Untuk melepaskan kunci tulis, metode tryRelease:

 1 protected final boolean tryRelease(int releases) { 2  //若锁的持有者不是当前线程,抛出异常 3  if (!isHeldExclusively()) 4   throw new IllegalMonitorStateException(); 5  //写锁的新线程数 6  int nextc = getState() - releases; 7  //如果独占模式重入数为0了,说明独占模式被释放 8  boolean free = exclusiveCount(nextc) == 0; 9  if (free)10   //若写锁的新线程数为0,则将锁的持有者设置为null11   setExclusiveOwnerThread(null);12  //设置写锁的新线程数13  //不管独占模式是否被释放,更新独占重入数14  setState(nextc);15  return free;16 }

Proses pelepasan kunci tulis relatif sederhana: pertama periksa apakah utas saat ini adalah pemegang kunci tulis, jika tidak ada pengecualian. Kemudian periksa apakah jumlah utas yang menulis kunci setelah rilis adalah 0, jika 0, itu berarti kunci tulis itu gratis, lepaskan sumber daya kunci dan setel utas penahan kunci ke nol, jika tidak rilis hanya kunci masuk kembali , dan tulisan tidak dapat ditulis. Utas yang terkunci dikosongkan.

Deskripsi: Metode ini digunakan untuk melepaskan sumber daya kunci tulis. Pertama, metode ini akan menentukan apakah utas adalah utas eksklusif. Jika ini bukan utas eksklusif, pengecualian akan dilempar. Jika tidak, jumlah kunci tulis setelah sumber daya adalah rilis dihitung. Jika 0 berarti berhasil. Rilis resource tidak akan ditempati, sebaliknya berarti resource masih ditempati. Diagram alir metode adalah sebagai berikut.

mengajukan
5. Akuisisi dan melepaskan kunci baca

Mirip dengan kunci tulis, implementasi sebenarnya dari kunci dan buka kunci baca sesuai dengan metode Sinkronisasi tryAcquireShared dan tryReleaseShared.

Untuk mendapatkan kunci baca, lihat metode tryAcquireShared

 1 protected final int tryAcquireShared(int unused) { 2  // 获取当前线程 3  Thread current = Thread.currentThread(); 4  // 获取状态 5  int c = getState(); 6  7  //如果写锁线程数 != 0 ,且独占锁不是当前线程则返回失败,因为存在锁降级 8  if (exclusiveCount(c) != 0 && 9   getExclusiveOwnerThread() != current)10   return -1;11  // 读锁数量12  int r = sharedCount(c);13  /*14  * readerShouldBlock():读锁是否需要等待(公平锁原则)15  * r < MAX_COUNT:持有线程小于最大数(65535)16  * compareAndSetState(c, c + SHARED_UNIT):设置读取锁状态17  */18  // 读线程是否应该被阻塞、并且小于最大值、并且比较设置成功19  if (!readerShouldBlock() &&20   r < MAX_COUNT &&21   compareAndSetState(c, c + SHARED_UNIT)) {22   //r == 0,表示第一个读锁线程,第一个读锁firstRead是不会加入到readHolds中23   if (r == 0) { // 读锁数量为024    // 设置第一个读线程25    firstReader = current;26    // 读线程占用的资源数为127    firstReaderHoldCount = 1;28   } else if (firstReader == current) { // 当前线程为第一个读线程,表示第一个读锁线程重入29    // 占用资源数加130    firstReaderHoldCount++;31   } else { // 读锁数量不为0并且不为当前线程32    // 获取计数器33    HoldCounter rh = cachedHoldCounter;34    // 计数器为空或者计数器的tid不为当前正在运行的线程的tid35    if (rh == null || rh.tid != getThreadId(current)) 36     // 获取当前线程对应的计数器37     cachedHoldCounter = rh = readHolds.get();38    else if (rh.count == 0) // 计数为039     //加入到readHolds中40     readHolds.set(rh);41    //计数+142    rh.count++;43   }44   return 1;45  }46  return fullTryAcquireShared(current);47 }

Metode sharedCount mewakili jumlah utas yang menempati kunci baca. Kode sumbernya adalah sebagai berikut:

static int sharedCount(int c) { return c >>> SHARED_SHIFT; }

Deskripsi: Geser 16 bit status ke kanan untuk mendapatkan jumlah utas untuk kunci baca, karena 16 bit status yang tinggi mewakili kunci baca, dan bit keenam belas yang sesuai mewakili jumlah kunci tulis.

Proses memperoleh kunci dengan kunci baca sedikit lebih rumit daripada kunci tulis. Pertama, tentukan apakah kunci tulis adalah 0 dan utas saat ini tidak menempati kunci eksklusif, dan langsung kembali; jika tidak, tentukan apakah utas pembaca perlu diblokir dan apakah jumlah kunci baca kurang dari nilai maksimum dan bandingkan Status setelan berhasil. Jika tidak ada kunci baca saat ini, utas pembaca pertama firstReader dan firstReaderHoldCount akan disetel; jika utas utas saat ini adalah utas pembaca pertama, firstReaderHoldCount akan dinaikkan; jika tidak, nilai objek HoldCounter yang sesuai dengan utas saat ini akan disetel. Diagram alirnya adalah sebagai berikut.

mengajukan
Catatan: Setelah pembaruan berhasil, nomor reentrant thread saat ini (baris 23 hingga 43) akan direkam dalam salinan firstReaderHoldCount atau readHolds (tipe ThreadLocal) dari thread ini. Ini untuk mengimplementasikan metode getReadHoldCount () yang ditambahkan di jdk1.6 Ya, metode ini dapat memperoleh berapa kali utas saat ini memasukkan kembali kunci bersama (jumlah total masuk kembali dari beberapa utas dicatat dalam status). Penambahan metode ini membuat kode jauh lebih rumit, tetapi prinsipnya adalah masih sangat sederhana: jika saat ini hanya ada satu utas, tidak perlu menggunakan ThreadLocal, dan langsung menyimpan jumlah reentrant di variabel anggota firstReaderHoldCount. Saat utas kedua muncul, variabel readHolds ThreadLocal harus digunakan. Masing-masing harus digunakan. utas memiliki salinannya sendiri. Digunakan untuk menyimpan nomor reentrant mereka sendiri.

metode fullTryAcquireShared:

final int fullTryAcquireShared(Thread current) { HoldCounter rh = null; for (;;) { // 无限循环  // 获取状态  int c = getState();  if (exclusiveCount(c) != 0) { // 写线程数量不为0   if (getExclusiveOwnerThread() != current) // 不为当前线程    return -1;  } else if (readerShouldBlock()) { // 写线程数量为0并且读线程被阻塞   // Make sure we're not acquiring read lock reentrantly   if (firstReader == current) { // 当前线程为第一个读线程    // assert firstReaderHoldCount > 0;   } else { // 当前线程不为第一个读线程    if (rh == null) { // 计数器不为空     //      rh = cachedHoldCounter;     if (rh == null || rh.tid != getThreadId(current)) { // 计数器为空或者计数器的tid不为当前正在运行的线程的tid      rh = readHolds.get();      if (rh.count == 0)       readHolds.remove();     }    }    if (rh.count == 0)     return -1;   }  }  if (sharedCount(c) == MAX_COUNT) // 读锁数量为最大值,抛出异常   throw new Error("Maximum lock count exceeded");  if (compareAndSetState(c, c + SHARED_UNIT)) { // 比较并且设置成功   if (sharedCount(c) == 0) { // 读线程数量为0    // 设置第一个读线程    firstReader = current;    //     firstReaderHoldCount = 1;   } else if (firstReader == current) {    firstReaderHoldCount++;   } else {    if (rh == null)     rh = cachedHoldCounter;    if (rh == null || rh.tid != getThreadId(current))     rh = readHolds.get();    else if (rh.count == 0)     readHolds.set(rh);    rh.count++;    cachedHoldCounter = rh; // cache for release   }   return 1;  } }}

Catatan: Dalam fungsi tryAcquireShared, jika tiga kondisi berikut tidak terpenuhi (apakah thread pembaca harus diblokir, kurang dari nilai maksimum, dan pengaturan perbandingan berhasil), fungsi fullTryAcquireShared akan dijalankan, yang digunakan untuk memastikan bahwa operasi terkait bisa berhasil. Logikanya mirip dengan logika tryAcquireShared, yang tidak lagi rumit.

Rilis kunci baca, metode tryReleaseShared

 1 protected final boolean tryReleaseShared(int unused) { 2  // 获取当前线程 3  Thread current = Thread.currentThread(); 4  if (firstReader == current) { // 当前线程为第一个读线程 5   // assert firstReaderHoldCount > 0; 6   if (firstReaderHoldCount == 1) // 读线程占用的资源数为1 7    firstReader = null; 8   else // 减少占用的资源 9    firstReaderHoldCount--;10  } else { // 当前线程不为第一个读线程11   // 获取缓存的计数器12   HoldCounter rh = cachedHoldCounter;13   if (rh == null || rh.tid != getThreadId(current)) // 计数器为空或者计数器的tid不为当前正在运行的线程的tid14    // 获取当前线程对应的计数器15    rh = readHolds.get();16   // 获取计数17   int count = rh.count;18   if (count <= 1) { // 计数小于等于119    // 移除20    readHolds.remove();21    if (count <= 0) // 计数小于等于0,抛出异常22     throw unmatchedUnlockException();23   }24   // 减少计数25   --rh.count;26  }27  for (;;) { // 无限循环28   // 获取状态29   int c = getState();30   // 获取状态31   int nextc = c - SHARED_UNIT;32   if (compareAndSetState(c, nextc)) // 比较并进行设置33    // Releasing the read lock has no effect on readers,34    // but it may allow waiting writers to proceed if35    // both read and write locks are now free.36    return nextc == 0;37  }38 }

Deskripsi: Metode ini berarti thread kunci baca melepaskan kunci. Pertama tentukan apakah utas saat ini adalah utas pembaca pertama firstReader, jika demikian, tentukan apakah jumlah sumber daya yang ditempati oleh utas pembaca pertama firstReaderHoldCount adalah 1, jika ya, setel utas pembaca pertama firstReader menjadi kosong, jika tidak, setel first Jumlah resource yang ditempati oleh thread pembaca firstReaderHoldCount dikurangi 1; jika thread saat ini bukan thread pembaca pertama, maka penghitung cache (penghitung yang sesuai dengan thread kunci pembaca terakhir) akan diperoleh terlebih dahulu, jika penghitung kosong atau tid tidak sama dengan nilai tid dari utas saat ini, Kemudian dapatkan penghitung dari utas saat ini. Jika hitungan pencacah kurang dari atau sama dengan 1, hapus penghitung yang sesuai dengan utas saat ini. hitungan hitungan pencacah kurang dari atau sama dengan 0, lempar pengecualian, dan kemudian kurangi hitungan. Bagaimanapun, itu akan memasuki loop tak terbatas, yang dapat memastikan bahwa status berhasil disetel. Diagram alirnya adalah sebagai berikut.

mengajukan
Dalam proses memperoleh dan melepaskan kunci baca, akan selalu ada objek. Pada saat yang sama, objek tersebut +1 saat kunci baca diperoleh oleh utas yang memperoleh, dan -1 saat kunci baca dilepaskan, objeknya adalah HoldCounter.

Untuk memahami HoldCounter, Anda harus terlebih dahulu memahami kunci baca. Seperti disebutkan sebelumnya, mekanisme implementasi internal dari kunci baca adalah kunci bersama.Untuk kunci bersama, kita sebenarnya dapat berpikir bahwa ini bukan konsep kunci, ini lebih seperti konsep penghitung. Operasi kunci bersama sama dengan operasi penghitung, dapatkan penghitung kunci bersama +1, dan lepaskan penghitung kunci bersama -1. Hanya setelah utas memperoleh kunci bersama, kunci bersama dapat dilepaskan dan dimasukkan kembali. Oleh karena itu, fungsi HoldCounter adalah jumlah kunci bersama yang dipegang oleh utas saat ini. Angka ini harus diikat ke utas, jika tidak pengecualian akan dilemparkan saat mengoperasikan kunci utas lainnya.

Pertama-tama, lihat bagian di mana kunci baca memperoleh kunci:

if (r == 0) {//r == 0,表示第一个读锁线程,第一个读锁firstRead是不会加入到readHolds中 firstReader = current; firstReaderHoldCount = 1;} else if (firstReader == current) {//第一个读锁线程重入 firstReaderHoldCount++; } else { //非firstReader计数 HoldCounter rh = cachedHoldCounter;//readHoldCounter缓存 //rh == null 或者 rh.tid != current.getId(),需要获取rh if (rh == null || rh.tid != current.getId())   cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0)  readHolds.set(rh); //加入到readHolds中 rh.count++; //计数+1}

Mengapa ada firstRead, firstReaderHoldCount? Daripada menggunakan kode lain secara langsung? Ini untuk masalah efisiensi. FirstReader tidak akan dimasukkan ke readHolds. Jika hanya ada satu kunci baca, ini akan menghindari pencarian readHolds. Mungkin saja kode ini masih belum memahami HoldCounter dengan baik. Mari kita lihat definisi firstReader dan firstReaderHoldCount:

private transient Thread firstReader = null;private transient int firstReaderHoldCount;

Kedua variabel ini relatif sederhana, satu mewakili utas, tentu saja utas adalah utas khusus, dan yang lainnya adalah jumlah reentrant dari firstReader.

Definisi HoldCounter:

static final class HoldCounter { int count = 0; final long tid = Thread.currentThread().getId();}

Di HoldCounter, hanya ada dua variabel, count dan tid, di mana count mewakili counter dan tid adalah id dari utas. Tetapi jika Anda ingin mengikat objek ke utas dan hanya merekam tid, itu pasti tidak cukup, dan HoldCounter tidak dapat memainkan peran mengikat objek sama sekali, itu hanya merekam utas tid.

Memang benar bahwa di Java, kita tahu bahwa jika utas dan objek terikat bersama, hanya ThreadLocal yang dapat diimplementasikan. Jadi sebagai berikut:

static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { public HoldCounter initialValue() {  return new HoldCounter(); }} ThreadLocalHoldCounter继承ThreadLocal,并且重写了initialValue方法。

Oleh karena itu, HoldCounter harus menjadi penghitung pada utas yang terikat, dan ThradLocalHoldCounter adalah ThreadLocal yang terikat oleh utas tersebut. Dari penjelasan di atas kita dapat melihat bahwa ThreadLocal mengikat HoldCounter ke utas saat ini, dan HoldCounter juga memegang Id utas, sehingga ketika kunci dilepaskan, kita dapat mengetahui apakah utas baca sebelumnya (cachedHoldCounter) yang di-cache di ReadWriteLock adalah utas saat ini. Keuntungannya adalah dapat mengurangi jumlah ThreadLocal.get (), karena ini juga merupakan operasi yang memakan waktu. Perlu dicatat bahwa alasan mengapa HoldCounter mengikat id utas dan tidak mengikat objek utas adalah untuk mencegah HoldCounter dan ThreadLocal terikat satu sama lain dan sulit bagi GC untuk melepaskannya (meskipun GC dapat dengan cerdas menemukannya referensi dan mengklaimnya kembali, tetapi ini membutuhkan Biaya tertentu), jadi sebenarnya, ini hanya untuk membantu GC mendapatkan kembali objek dengan cepat.

Tiga, ringkasan

Melalui analisis kode sumber di atas, kita dapat menemukan sebuah fenomena:

Dalam kasus utas yang memegang kunci baca, utas tidak dapat memperoleh kunci tulis (karena ketika memperoleh kunci tulis, jika kunci baca saat ini ditemukan ditempati, akuisisi segera gagal, terlepas dari apakah kunci baca ditahan. oleh utas saat ini).

Saat utas menahan kunci tulis, utas dapat terus mendapatkan kunci baca (jika kunci tulis ditemukan ditempati saat kunci baca diperoleh, akuisisi akan gagal hanya jika kunci tulis tidak ditempati oleh utas saat ini ).

Pikirkan baik-baik, desain ini masuk akal: karena ketika utas memperoleh kunci baca, utas lain mungkin juga memegang kunci baca pada saat yang sama, sehingga utas yang memperoleh kunci baca tidak dapat "ditingkatkan" menjadi kunci tulis ; Utas yang dikunci harus memiliki akses eksklusif ke kunci baca-tulis, sehingga dapat terus memperoleh kunci baca. Saat memperoleh kunci tulis dan kunci baca pada saat yang sama, utas juga dapat melepaskan kunci tulis dan terus menahan kunci baca. Kunci tulis tersebut disebut "Downgrade" untuk kunci baca.

Singkatnya:

Jika utas ingin memegang kunci tulis dan kunci baca pada saat yang sama, utas harus memperoleh kunci tulis terlebih dahulu dan kemudian mendapatkan kunci baca; kunci tulis dapat "diturunkan versinya" menjadi kunci baca; kunci baca tidak dapat "ditingkatkan" untuk menulis kunci.

Selamat datang untuk memperhatikan
akun publik [Kode petani mekar] Belajar tumbuh bersama, saya akan selalu berbagi barang kering jawa, dan saya juga akan membagikan materi pembelajaran, kursus dan koleksi wawancara secara gratis.
Balas: [Komputer] [Mode Desain] [ Wawancara] Ada kejutan

Comments

Popular posts from this blog

Replika open source di GitHub: Diablo 2

Laporan Analisis Tren Industri Ritel 2021

[Pola desain] Pola Singleton (Pola Singleton)