Kata pengantar
Sinkronisasi dalam satu JVM mudah ditangani. Cukup gunakan kunci yang disediakan oleh JDK secara langsung, tetapi sinkronisasi silang-proses jelas tidak mungkin. Dalam hal ini, Anda harus mengandalkan pihak ketiga. Saya menggunakan Redis di sini, dan tentu saja ada banyak metode implementasi lainnya. Faktanya, prinsip yang didasarkan pada implementasi Redis cukup sederhana. Sebelum membaca kode, Anda disarankan untuk memeriksa prinsipnya terlebih dahulu. Setelah membaca kode, harus lebih mudah dipahami.
Saya tidak mengimplementasikan antarmuka java.util.concurrent.locks.Lock JDK, tetapi menyesuaikan satu, karena ada metode newCondition di JDK. Saya belum menerapkannya untuk saat ini. Kunci ini menyediakan 5 varian metode kunci. Anda dapat memilih mana yang akan digunakan untuk mendapatkan kunci. Ide saya adalah yang terbaik adalah menggunakan metode ini dengan pengembalian batas waktu. Karena jika ini bukan masalahnya, jika Redis digantung, utas akan selalu berada di loop dead (tentang ini, itu harus dioptimalkan lebih lanjut. Jika Redis digantung, operasi Jedis pasti akan melempar pengecualian dan sebagainya. Anda dapat mendefinisikan mekanisme untuk memberi tahu pengguna yang menggunakan kunci ini ketika Redis digantung, atau benang))
Paket cc.lixiaohui.lock; import java.util.concurrent.timeunit; kunci antarmuka publik { / *** Memblokir kunci akuisisi, tidak menanggapi interupsi* / void lock; / ** * Memblokir kunci akuisisi, tidak menanggapi interupsi * * @Throws InterruptedException */ void Lockinterricture melempar interruptedException; / *** Cobalah untuk mendapatkan kunci, segera kembali tanpa mendapatkannya, bukan memblokir*/ boolean trylock; / ** * Kunci akuisisi pemblokiran secara otomatis dikembalikan dengan batas waktu, tidak menanggapi interupsi * * @param waktu * @param unit * @return {@code true} Jika kunci berhasil diperoleh, {@code false} Jika kunci tidak diambil dalam waktu yang ditentukan */ boolean trylock (waktu lama), waktu lama, waktu lama) waktu); / *** Kunci akuisisi pemblokiran secara otomatis dikembalikan berdasarkan batas waktu, interupsi respons** @param time* @param unit* @return {@code true} Jika kunci berhasil diperoleh, {@code false} Jika lock tidak dapat dikembalikan dalam waktu yang ditentukan* @throws interrupted exception saat ini mencoba untuk mengatasinya saat ini untuk mendapatkan kunci yang dicoba saat ini untuk mencoba, @throws @throws @code -lock yang diinterupsi saat ini mencoba untuk mencoba terkunci saat ini, mencoba ke utas saat ini, mencoba, mencoba, terobosan saat ini, @throws @throws @code -lock, @throws cloteun @throws @code @code @Chrows, unit) melempar interupsi Exception; / *** Release Lock*/ void Unlock; }Lihatlah implementasinya yang abstrak:
Paket cc.lixiaohui.lock; import java.util.concurrent.timeunit;/*** Implementasi kerangka kunci, langkah -langkah nyata untuk memperoleh kunci diimplementasikan oleh subkelas. * * @Author lixiaohui * * /kelas abstrak publik Abstraklock mengimplementasikan kunci { /** * <pe> * apakah visibilitas perlu dijamin di sini layak dibahas, karena itu adalah kunci yang terdistribusi, * 1. Tidak ada yang mungkin untuk beberapa visibilitas. dijamin. * </pe> */ Boolean volatil yang dilindungi terkunci; / ** * Utas yang saat ini memegang kunci di JVM (jika ada) */ Private Thread ExclusiveOwnerThread; public void lock {coba {lock (false, 0, null, false); } catch (InterruptedException e) {// TODO abaikan}} public void lockerTericly melempar interruptedException {lock (false, 0, null, true); } public boolean trylock (lama, unit timeunit) {coba {return lock (true, time, unit, false); } catch (InterruptedException e) {// TODO abaikan} return false; } public boolean trylockinterricture (lama, unit timeunit) melempar interruptedException {return lock (true, time, unit, true); } public void unlock {// TODO Periksa apakah utas saat ini menahan kunci IF (thread.currentThread! = getExclusiveOwnerThread) {lempar baru ilegalmonitorstateException ("Thread saat ini tidak menahan kunci"); } unlock0; setExclusiveOwnerThread (null); } Protected void setExclusiveOwnerThread (Thread Thread) {ExclusiveOwnerThread = Thread; } utas akhir yang dilindungi getExclusiveOwnerThread {return ExclusiveOwnerThread; } terlindungi abstrak void unlock0; / ** * Implementasi Kunci Akuisisi Pemblokiran * * @param Usetimeout * @param Time * @param Unit * @param mengganggu apakah akan merespons interupsi * @return * @throws interruptedException */ Lindung abstrak interupsi (boolean exetimeout, lama, unit timeunit, boolean interuprupruprupruprupruprupruprupruprupruprupted (Boolean Exetimeout, lama, unit timeunit, boolean interruprupt) Throwruprupruprupruprupruprupruprupruprupruprupted Berdasarkan implementasi akhir REDIS, kode kunci untuk memperoleh dan melepaskan kunci ada dalam metode lock dan metode unlock0 dari kelas ini. Anda hanya dapat melihat kedua metode ini dan menulisnya sepenuhnya sendiri:
package cc.lixiaohui.lock;import java.util.concurrent.TimeUnit;import redis.clients.jedis.Jedis;/** * <pre> * Distributed lock implemented by SETNX operation based on Redis* * It is best to use lock(long time, TimeUnit unit) when acquiring locks to avoid network problems causing threads to block all the time* * <a href = "http://redis.io/commands/setnx"> Referensi Operasi Setnc </a> * </pre> * @author lixiaohui * */kelas publik redisbaseddistributedlock memperluas abstrak abstrak {private jedis jedis; // Nama kunci string yang dilindungi kunci; // Durasi validitas kunci (MS) yang dilindungi Lockexpires panjang; RedisBasedDistributedLock publik (Jedis Jedis, String Lockkey, Long LockExpires) {this.jedis = jedis; this.lockkey = lockkey; this.lockexpires = lockExpires; } // Implementasi Kunci Akuisisi Pemblokiran Kunci Boolean Lock (Boolean Usetimeout, Long Time, TimeUnit Unit, Boolean Interrupt) melempar InterruptedException {if (interrupt) {checkInterruption; } Long start = System.CurrentTimeMillis; waktu lama = unit.tomillis (waktu); // if! usetimeout, maka itu tidak berguna saat (usetimeout? isTimeout (start, timeout): true) {if (interrupt) {checkInterruption; } Long lockExpiretime = system.currentTimeMillis + lockexpires + 1; // lock timeout string stringoflockExpiretime = string.valueof (lockexpiretime); if (jedis.setnx (lockkey, stringoflockExpiretime) == 1) {// diperoleh lock // todo berhasil memperoleh kunci, atur pengidentifikasi yang relevan terkunci = true; setExclusiveOwnerThread (thread.currentthread); Kembali Benar; } String value = jedis.get (lockkey); if (value! = null && iStiMeExpired (value)) {// lock sudah kedaluwarsa // Asumsikan bahwa beberapa utas (non-single JVM) datang ke sini pada waktu yang sama string oldValue = jedis.getSet (lockkey, stringoflockexpiretime); // getset is atomic // But the oldValue obtained by each thread when it comes here is definitely impossible to be the same (because getset is atomic) // The oldValue obtained by joining is still expired, then it means that the lock is obtained if (oldValue != null && isTimeExpired(oldValue)) { // TODO successfully obtains the lock, set the relevant identifier locked = true; setExclusiveOwnerThread (thread.currentthread); Kembali Benar; }} else {// TODO LOCK tidak kedaluwarsa, masukkan ulang loop berikutnya}} return false; } public boolean trylock {lockExpiretime = System.currentTimeMillis + lockexpires + 1; // lock timeout time stringoflockExpiretime = string.valueof (lockExpiretime); if (jedis.setnx (lockkey, stringoflockExpiretime) == 1) {// dapatkan kunci // todo berhasil memperoleh kunci, atur pengidentifikasi yang relevan terkunci = true; setExclusiveOwnerThread (thread.currentthread); Kembali Benar; } String value = jedis.get (lockkey); if (value! = null && iStiMeExpired (value)) {// lock sudah kedaluwarsa // Asumsikan beberapa utas (bukan jvm tunggal) datang ke sini pada waktu yang sama string oldvalue = jedis.getset (lockkey, stringoflockExpiretime); // getset adalah atomik // tetapi nilai tua yang diperoleh oleh setiap utas ketika datang ke sini pasti tidak mungkin (karena getset adalah atom) // jika nilai tua yang Anda dapatkan masih kedaluwarsa, maka itu berarti Anda telah mendapatkan kunci jika (oldvalue! = null && lockier yang disetel (oldvalue)) {// to doo diperkirakan dengan sukses = yang diperoleh dengan sukses = yang diperoleh ke -doo yang dipertahankan dengan sukses = yang sukses = yang sukses = yang sukses = yang bertanggung jawab ke doaker = yang berhasil, yang berhasil, yang berhasil, setExclusiveOwnerThread (thread.currentthread); Kembali Benar; }} else {// toDo lock tidak kedaluwarsa, masukkan ulang loop berikutnya} return false; } /*** Pertanyaan Jika kunci ini dipegang oleh utas apa pun. * * @return {@code true} Jika ada utas yang memegang kunci ini dan * {@code false} sebaliknya */ public boolean terislocked {if (terkunci) {return true; } else {string value = jedis.get (lockkey); // todo sebenarnya ada masalah di sini. Pikirkan: Ketika metode GET mengembalikan nilai, asumsikan bahwa nilainya telah kedaluwarsa, // Pada saat ini, simpul lain menetapkan nilainya, dan kunci dipegang oleh utas lain (simpul memegang), dan penilaian berikutnya // tidak dapat mendeteksi situasi ini. Namun, masalah ini seharusnya tidak menyebabkan masalah lain, karena tujuan metode ini adalah // bukan kontrol sinkron, itu hanya laporan status kunci. Return! ISTimEEXPired (nilai); }} @Override Protected void unlock0 {// todo menentukan apakah kunci kedaluwarsa nilai string = jedis.get (lockkey); if (! istimeExpired (value)) {dounlock; }} private void checkInterruption melempar InterruptedException {if (thread.currentThread.isInterrupted) {throw new InterruptedException; }} private boolean istimeExpired (nilai string) {return long.parselong (value) <system.currentTimemillis; } private boolean isTimeOut (start panjang, waktu lama) {return start + timeout> system.currentTimeMillis; } private void dounlock {jedis.del (lockkey); }} Jika Anda mengubah metode implementasi di masa mendatang (seperti zookeeper , dll.), Maka Anda dapat secara langsung mewarisi AbstractLock dan mengimplementasikan L ock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt) dan Metode unlock0 (yang disebut Abstraksi)
tes
Simulasi penanam ID global dan rancang kelas IDGenerator . Kelas ini bertanggung jawab untuk menghasilkan ID tambahan global. Kodenya adalah sebagai berikut:
Paket cc.lixiaohui.lock; import java.math.biginteger; import java.util.concurrent.timeunit;/** * Simulasi generasi ID * @author lixiaohui */IDGenerator kelas publik {private static Biginteger ID = biginteger.valueof (0); kunci kunci akhir pribadi; private static final biginteger increment = biginteger.valueof (1); IDGenerator publik (kunci kunci) {this.lock = lock; } public String getAndIncrement {if (lock.trylock (3, timeunit.seconds)) {coba {// TODO Dapatkan kunci di sini dan akses sumber daya kritis pengembalian getAndIncrement0; } akhirnya {lock.unlock; }} return null; // return getAndIncrement0; } string private getAndIncrement0 {string s = id.toString; id = id.add (increment); kembali S; }} Tes Logika Utama: Buka dua utas dalam JVM yang sama untuk mengulangi mematikan (tidak ada interval antara loop, jika ada satu, tes akan tidak ada artinya) untuk mendapatkan ID (saya bukan loop mati tetapi berjalan untuk 20 -an), dapatkan ID dan simpan di Set yang sama. Sebelum disimpan, periksa apakah ID ada di set . Jika sudah ada, biarkan kedua utas berhenti. Jika program dapat berjalan 20 detik secara normal, itu berarti bahwa kunci terdistribusi ini dapat memenuhi persyaratan. Efek dari tes tersebut harus sama dengan JVM yang berbeda (yaitu, dalam lingkungan terdistribusi nyata). Berikut ini adalah kode kelas tes:
Paket cc.lixiaohui.distributedlock.distributedlock; impor java.util.hashset; import java.util.set; org.junit.test; impor redis.clients.jedis.jedis; impor cc.lixiaohui.lock.idgenerator; impor cc.lixiaohui.lock.idgenerator; cc.lixiaohui.lock.redisBasedDistributedLock; kelas publik IDGenerATortest {private static set <string> generatedIds = hashset baru <string>; string final private static lock_key = "lock.lock"; private static final long lock_expire = 5 * 1000; @Test public void test melempar InterruptedException {jedis jedis1 = new jedis ("localhost", 6379); Lock lock1 = redisbasedDistributedLock baru (jedis1, lock_key, lock_expire); IDGenerator G1 = IDGenerator baru (LOCK1); IDConsumemission consume1 = IDConsumemission baru (G1, "consume1"); Jedis jedis2 = jedis baru ("localhost", 6379); Lock lock2 = redisbasedDistributedLock baru (jedis2, lock_key, lock_expire); IDGenerator G2 = IDGenerator baru (LOCK2); IDConsumemission consume2 = IDConsumemission baru (G2, "consume2"); Utas t1 = utas baru (consume1); Utas T2 = utas baru (consume2); t1.start; t2.start; Thread.sleep (20 * 1000); // Biarkan dua utas berjalan selama 20 detik idconsumemission.stop; t1.join; t2.join; } static string time {return string.valueof (System.CurrentTimeMillis / 1000); } kelas statis IDConsumemission mengimplementasikan runnable {private IDGenerator IDGenerator; nama string pribadi; berhenti boolean statis privat; IDConsumemission publik (idgenerator idgenerator, nama string) {this.idgenerator = idgenerator; this.name = name; } public static void stop {stop = true; } public void run {System.out.println (waktu + ": consume" + name + "start"); while (! stop) {string id = idgenerator.getAndincrement; if (generatedIds.contains (id)) {System.out.println (Time + ": ID duplikat dihasilkan, id =" + id); hentikan = true; melanjutkan; } generatedIds.add (id); System.out.println (Time + ": consume" + name + "Tambahkan id =" + id); } System.out.println (waktu + ": consume" + name + "done"); }}}Untuk lebih jelasnya, cara saya menghentikan dua utas di sini tidak terlalu bagus. Saya melakukan ini untuk kenyamanan, karena ini hanya tes, jadi sebaiknya tidak melakukan ini.
Hasil tes
Ada terlalu banyak hal yang dicetak di usia 20 -an. Yang dicetak di bagian depan clear dan hanya tersedia saat menjalankan hampir selesai. Tangkapan layar di bawah ini. Ini menunjukkan bahwa kunci ini bekerja secara normal:
Ketika IDGererator tidak terkunci (yaitu, metode getAndIncrement IDGererator tidak menguncinya ketika memperoleh id secara internal), tes tidak akan lulus, dan ada kemungkinan yang sangat tinggi bahwa ia akan berhenti di tengah jalan. Berikut ini adalah hasil tes saat kunci tidak terkunci:
Ini membutuhkan waktu kurang dari 1 detik:
Yang ini membutuhkan waktu kurang dari 1 detik:
Kesimpulan
OK, di atas adalah semua tentang Java yang menerapkan kunci terdistribusi berdasarkan Redis. Jika Anda menemukan masalah, Anda berharap dapat memperbaikinya. Saya harap artikel ini dapat membantu Anda belajar dan bekerja. Jika Anda memiliki pertanyaan, Anda dapat meninggalkan pesan untuk berkomunikasi.