Rumah >Java >javaTutorial >Apakah kaedah pelaksanaan mengunci di Jawa?
Seperti namanya, ia merujuk kepada sikap konservatif terhadap pengubahsuaian data dan kepercayaan bahawa orang lain juga akan mengubah suai data. Oleh itu, semasa mengendalikan data, data akan dikunci sehingga operasi selesai. Dalam kebanyakan kes, penguncian pesimis bergantung pada mekanisme penguncian pangkalan data untuk memastikan eksklusiviti maksimum operasi. Jika masa mengunci terlalu lama, pengguna lain tidak akan dapat mengaksesnya untuk masa yang lama, yang akan menjejaskan akses serentak program Pada masa yang sama, ini juga akan memberi impak yang besar terhadap prestasi pangkalan data untuk urus niaga yang lama, overhed sedemikian selalunya tidak dapat ditanggung.
Jika ia adalah sistem yang berdiri sendiri, kita boleh menggunakan kata kunci disegerakkan yang disertakan dengan JAVA untuk mengunci sumber dengan menambahkannya pada kaedah atau blok penyegerakan Jika ia adalah sistem teragih, kita boleh menggunakan mekanisme mengunci pangkalan data itu sendiri untuk mencapai ini.
select * from 表名 where id= #{id} for update
Apabila menggunakan penguncian pesimis, kita mesti memberi perhatian kepada tahap kunci Apabila MySQL innodb mengunci, kunci baris hanya akan digunakan jika kunci utama atau (medan indeks) dinyatakan dengan jelas; dilaksanakan. Kunci keseluruhan jadual dan prestasi akan menjadi lemah. Apabila menggunakan penguncian pesimis, kita mesti mematikan atribut autocommit pangkalan data MySQL kerana mysql menggunakan mod autocommit secara lalai. Penguncian pesimis sesuai untuk senario di mana terdapat banyak tulisan, dan keperluan prestasi serentak tidak tinggi.
Kunci optimis, anda boleh meneka makna anggaran dari makna literal Ia sangat optimistik apabila mengendalikan data, memikirkan bahawa orang lain tidak akan mengubah suai data pada masa yang sama , jadi kunci optimis tidak akan diaktifkan Kunci hanya akan mengesan secara rasmi sama ada data bercanggah atau tidak apabila kemas kini diserahkan. Jika konflik ditemui, mesej ralat dikembalikan dan pengguna boleh memutuskan apa yang perlu dilakukan, mekanisme yang gagal. Jika tidak, lakukan operasi ini.
dibahagikan kepada tiga peringkat: pembacaan data, pengesahan penulisan dan penulisan data.
Jika ia adalah sistem yang berdiri sendiri, kami boleh melaksanakannya berdasarkan CAS JAVA ialah operasi atom yang dilaksanakan dengan bantuan perbandingan dan pertukaran perkakasan.
Jika ia adalah sistem teragih, kami boleh menambah medan nombor versi pada jadual pangkalan data, seperti versi.
update 表 set ... , version = version +1 where id= #{id} and version = #{version}
Sebelum beroperasi, baca nombor versi rekod dahulu Semasa mengemas kini, bandingkan nombor versi melalui pernyataan SQL untuk melihat sama ada ia konsisten. Jika konsisten, kemas kini data. Jika tidak, versi akan dibaca semula dan operasi di atas akan dicuba semula.
disegerakkan, ReentrantLock, dsb. dalam JAVA semuanya menyelesaikan masalah pengecualian bersama sumber bagi penggunaan mesin tunggal bagi aplikasi tunggal. Dengan perkembangan pesat perniagaan, apabila satu aplikasi berkembang menjadi kelompok teragih, berbilang benang dan berbilang proses diedarkan pada mesin yang berbeza, dan strategi kunci kawalan penyelarasan mesin tunggal yang asal menjadi tidak sah
Pada masa ini masa kita perlu memperkenalkan Kunci yang diedarkan menyelesaikan mekanisme pengecualian bersama merentas mesin untuk mengawal akses kepada sumber yang dikongsi.
Apakah syarat yang diperlukan untuk kunci yang diedarkan:
Fungsi pengecualian bersama sumber yang sama sebagai sistem yang berdiri sendiri, yang merupakan asas kunci
Pemerolehan dan pelepasan kunci berprestasi tinggi
Ketersediaan tinggi
Kemasukan Semula
Ada mekanisme kegagalan kunci untuk mengelakkan kebuntuan
Tidak menghalang, tidak kira sama ada kunci diperolehi atau tidak, ia mesti boleh kembali dengan cepat
Terdapat banyak cara untuk melaksanakannya Diverse, berdasarkan pangkalan data, Redis, dan Zookeeper, dsb. Berikut ialah kaedah pelaksanaan berasaskan Redis arus perdana:
SET key unique_value [EX seconds] [PX milliseconds] [NX|XX]
Dengan arahan atom, jika pelaksanaan berjaya, 1 dikembalikan, yang bermaksud menambah Kunci berjaya. Nota: unique_value ialah pengecam unik yang dijana oleh klien untuk membezakan operasi kunci daripada klien yang berbeza Beri perhatian khusus untuk membuka kunci terlebih dahulu sama ada unique_value adalah klien terkunci. Lagipun, kami tidak boleh memadamkan kunci yang ditambahkan oleh pelanggan lain.
Membuka kunci: Membuka kunci mempunyai dua operasi perintah, yang memerlukan penggunaan skrip Lua untuk memastikan atomicity.
// 先比较 unique_value 是否相等,避免锁的误释放 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
Dengan prestasi tinggi Redis, Redis melaksanakan kunci teragih yang juga merupakan kaedah pelaksanaan arus perdana semasa. Tetapi segala-galanya mempunyai kebaikan dan keburukan Jika pelayan terkunci jatuh dan nod hamba tidak mempunyai masa untuk menyandarkan data, pelanggan lain juga boleh mendapatkan kunci.
Untuk menyelesaikan masalah ini, Redis secara rasmi mereka bentuk kunci Redlock yang diedarkan.
Idea asas: Biarkan permintaan pelanggan dikunci dengan berbilang nod Redis bebas secara selari Jika operasi mengunci boleh berjaya diselesaikan pada lebih separuh daripada nod, maka kami menganggap bahawa pelanggan telah berjaya memperoleh kunci pengedaran. , jika tidak, kunci akan gagal.
Kunci Reentrant, juga dipanggil kunci rekursif, bermakna apabila benang yang sama memanggil kaedah luar untuk memperoleh kunci, maka memasuki kaedah dalam akan memperoleh kunci secara automatik.
Terdapat pembilang di dalam kunci objek atau kunci kelas Setiap kali benang memperoleh kunci, pembilang +1 apabila membuka kunci, pembilang -1.
Bilangan kunci sepadan dengan bilangan buka kunci dan buka kunci muncul secara berpasangan.
ReentrantLock dan disegerakkan dalam Java ialah kedua-dua kunci reentrant. Satu faedah kunci masuk semula ialah kebuntuan boleh dielakkan pada tahap tertentu.
自旋锁是采用让当前线程不停地在循环体内执行,当循环的条件被其他线程改变时才能进入临界区。自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不断增加时,性能下降明显,因为每个线程都需要执行,会占用CPU时间片。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。
自旋锁缺点:
可能引发死锁。
可能占用 CPU 的时间过长。
我们可以设置一个 循环时间 或 循环次数,超出阈值时,让线程进入阻塞状态,防止线程长时间占用 CPU 资源。JUC 并发包中的 CAS 就是采用自旋锁,compareAndSet 是CAS操作的核心,底层利用Unsafe对象实现的。
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
如果内存中 var1 对象的var2字段值等于预期的 var5,则将该位置更新为新值(var5 + var4),否则不进行任何操作,一直重试,直到操作成功为止。
CAS 包含了Compare和Swap 两个操作,如何保证原子性呢?CAS 是由 CPU 支持的原子操作,其原子性是在硬件层面进行控制。
特别注意,CAS 可能导致 ABA 问题,我们可以引入递增版本号来解决。
独享锁,也有人叫它排他锁。无论读操作还是写操作,只能有一个线程获得锁,其他线程处于阻塞状态。
缺点:读操作并不会修改数据,而且大部分的系统都是 读多写少,如果读读之间互斥,大大降低系统的性能。下面的 共享锁 会解决这个问题。
像Java中的 ReentrantLock 和 synchronized 都是独享锁。
共享锁是指允许多个线程同时持有锁,一般用在读锁上。读锁的共享锁可保证并发读是非常高效的。读写,写读 ,写写的则是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
ReentrantReadWriteLock,其读锁是共享锁,其写锁是独享锁。
如果对某个资源是读操作,那多个线程之间并不会相互影响,可以通过添加读锁实现共享。如果有修改动作,为了保证数据的并发安全,此时只能有一个线程获得锁,我们称之为 写锁。读读是共享的;而 读写、写读 、写写 则是互斥的。
像 Java中的 ReentrantReadWriteLock 就是一种 读写锁。
公平锁:多个线程按照申请锁的顺序去获得锁,所有线程都在队列里排队,先来先获取的公平性原则。
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,CPU 唤醒下一个阻塞线程有系统开销。
非公平锁:多个线程不按照申请锁的顺序去获得锁,而是同时以插队方式直接尝试获取锁,获取不到(插队失败),会进入队列等待(失败则乖乖排队),如果能获取到(插队成功),就直接获取到锁。
优点:可以减少 CPU 唤醒线程的开销,整体的吞吐效率会高点。
缺点:可能导致队列中排队的线程一直获取不到锁或者长时间获取不到锁,活活饿死。
Java 多线程并发操作,我们操作锁大多时候都是基于 Sync 本身去实现的,而 Sync 本身却是 ReentrantLock 的一个内部类,Sync 继承 AbstractQueuedSynchronizer。
像 ReentrantLock 默认是非公平锁,我们可以在构造函数中传入 true,来创建公平锁。
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
可中断锁:指一个线程因为没有获得锁在阻塞等待过程中,可以中断自己阻塞的状态。不可中断锁:恰恰相反,如果锁被其他线程获取后,当前线程只能阻塞等待。如果持有锁的线程一直不释放锁,那其他想获取锁的线程就会一直阻塞。
内置锁 synchronized 是不可中断锁,而 ReentrantLock 是可中断锁。
ReentrantLock获取锁定有三种方式:
lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于阻塞状态,直到该线程获取锁。
tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false。
tryLock(masa tamat masa, unit TimeUnit), jika kunci diperolehi, ia akan kembali benar dengan serta-merta Jika benang lain memegang kunci, ia akan menunggu masa yang diberikan oleh parameter Semasa proses menunggu, jika Jika kunci diperoleh, ia kembali benar Jika masa menunggu, ia kembali palsu.
kunciInterruptibly(), kembali serta-merta jika kunci diperolehi; jika kunci tidak diperoleh, benang disekat sehingga kunci diperoleh atau benang diganggu oleh benang lain.
Kunci bersegmen sebenarnya adalah sejenis reka bentuk kunci, tujuannya adalah untuk memperhalusi butiran kunci, bukan jenis kunci tertentu. Untuk ConcurrentHashMap, pelaksanaan konkurensinya adalah untuk mencapai operasi serentak yang cekap dalam bentuk kunci bersegmen.
Kunci segmen dalam ConcurrentHashMap dipanggil Segmen, iaitu struktur yang serupa dengan HashMap (pelaksanaan HashMap dalam JDK7), iaitu, ia mempunyai tatasusunan Kemasukan secara dalaman dan setiap elemen dalam tatasusunan adalah dipautkan senarai; pada masa yang sama Satu lagi ReentrantLock (Segmen mewarisi ReentrantLock).
Apabila anda perlu meletakkan elemen, anda tidak mengunci keseluruhan HashMap Sebaliknya, anda mula-mula tahu segmen mana untuk meletakkannya melalui kod cincang, dan kemudian mengunci segmen ini, jadi apabila berbilang benang meletakkan , sisipan selari disokong selagi ia tidak diletakkan dalam segmen yang sama.
Sebelum JDK 1.6, disegerakkan masih merupakan kunci kelas berat dengan kecekapan yang agak rendah. Walau bagaimanapun, selepas JDK 1.6, JVM dioptimumkan disegerakkan untuk meningkatkan kecekapan pemerolehan dan pelepasan kunci, dan memperkenalkan kunci berat sebelah dan kunci ringan Sejak itu, terdapat empat keadaan kunci: tiada kunci, kunci berat sebelah dan kunci Tahap ringan , kunci kelas berat. Keempat-empat negeri ini akan menaik taraf secara beransur-ansur dengan persaingan dan tidak boleh diturunkan.
Bebas kunci tidak mengunci sumber Semua rangkaian boleh mengakses dan mengubah suai sumber yang sama, tetapi hanya satu rangkaian boleh mengaksesnya pada masa yang sama berjaya. Inilah yang sering kita panggil penguncian optimistik.
berat sebelah ke arah utas pertama yang mengakses kunci Apabila blok kod disegerakkan dilaksanakan buat kali pertama, bendera kunci dalam pengepala objek diubah suai melalui CAS dan. objek kunci menjadi kunci berat sebelah.
Apabila thread mengakses blok kod disegerakkan dan memperoleh kunci, ID thread bias kunci akan disimpan dalam Mark Word. Apabila benang masuk dan keluar dari blok disegerakkan, ia tidak lagi mengunci dan membuka kunci melalui operasi CAS, tetapi mengesan sama ada Mark Word menyimpan kunci bias yang menghala ke utas semasa. Pemerolehan dan pelepasan kunci ringan bergantung pada berbilang arahan atom CAS, manakala kunci berat sebelah hanya perlu bergantung pada satu arahan atom CAS apabila menggantikan ThreadID.
Selepas melaksanakan blok kod disegerakkan, benang tidak akan melepaskan kunci bias secara aktif. Apabila benang melaksanakan blok kod penyegerakan untuk kali kedua, benang akan menentukan sama ada benang yang memegang kunci pada masa ini adalah dirinya sendiri (ID benang yang memegang kunci juga berada dalam pengepala objek), dan jika ya, ia akan diteruskan pelaksanaan secara normal. Memandangkan kunci belum dilepaskan sebelum ini, tidak perlu mengunci semula di sini Kunci berat sebelah hampir tiada overhed tambahan dan mempunyai prestasi yang sangat tinggi.
Kunci bias hanya akan melepaskan kunci apabila benang lain cuba bersaing untuk kunci bias. Berkenaan pembatalan kunci berat sebelah, anda perlu menunggu titik keselamatan global, iaitu, apabila tiada kod bait sedang dilaksanakan pada masa tertentu, ia akan menjeda benang yang memiliki kunci berat sebelah itu dahulu, dan kemudian menentukan sama ada objek kunci dikunci. Jika benang tidak aktif, pengepala objek ditetapkan kepada keadaan bebas kunci, dan kunci berat sebelah dibatalkan, kembali kepada keadaan bebas kunci (bit bendera ialah 01) atau kunci ringan (bit bendera ialah 00).
Kunci berat sebelah bermakna apabila sekeping kod penyegerakan sentiasa diakses oleh utas yang sama, iaitu, apabila tiada persaingan antara berbilang utas, maka utas itu akan memperoleh kunci secara automatik pada capaian berikutnya, sekali gus mengurangkan risiko memperoleh kunci yang ditimbulkan.
Kunci semasa ialah kunci pincang. Jika berbilang benang bersaing untuk kunci pada masa yang sama, kunci pincang akan dinaik taraf kepada kunci ringan. Kunci ringan percaya bahawa walaupun persaingan wujud, idealnya tahap persaingan adalah sangat rendah, dan kunci diperoleh melalui putaran.
Terdapat dua situasi apabila kunci ringan diperoleh:
Apabila fungsi kunci pincang dimatikan.
Berbilang benang bersaing untuk kunci pincang menyebabkan kunci pincang dinaik taraf kepada kunci ringan. Sebaik sahaja benang kedua menyertai pertandingan kunci, kunci berat sebelah dinaik taraf kepada kunci ringan (kunci putaran).
Teruskan pertandingan kunci dalam keadaan kunci ringan Benang yang belum meraih kunci akan berputar dan terus bergelung untuk menentukan sama ada kunci itu berjaya diperoleh. Operasi memperoleh kunci sebenarnya untuk mengubah suai bendera kunci dalam pengepala objek melalui CAS. Mula-mula bandingkan sama ada bendera kunci semasa "dilepaskan", dan jika ya, tetapkan ia kepada "dikunci". Jika kunci dicekup, maka benang mengubah suai maklumat pemegang kunci semasa kepada dirinya sendiri.
如果线程的竞争很激励,线程的自旋超过了一定次数(默认循环10次,可以通过虚拟机参数更改),将轻量级锁升级为重量级锁(依然是 CAS 修改锁标志位,但不修改持有锁的线程ID),当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒。
重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。简言之,就是所有的控制权都交给了操作系统,由操作系统来负责线程间的调度和线程的状态变更。而这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资。
锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。
举个例子:有个循环体,内部。
for(int i=0;i<size;i++){ synchronized(lock){ ...业务处理,省略 } }
经过锁粗化的代码如下:
synchronized(lock){ for(int i=0;i<size;i++){ ...业务处理,省略 } }
锁消除指的是在某些情况下,JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的。
锁消除的依据是逃逸分析的数据支持,如 StringBuffer 的 append() 方法,或 Vector 的 add() 方法,在很多情况下是可以进行锁消除的,比如以下这段代码:
public String method() { StringBuffer sb = new StringBuffer(); for (int i = 0; i < 10; i++) { sb.append("i:" + i); } return sb.toString(); }
以上代码经过编译之后的字节码如下:
从上述结果可以看出,之前我们写的线程安全的加锁的 StringBuffer 对象,在生成字节码之后就被替换成了不加锁不安全的 StringBuilder 对象了,原因是 StringBuffer 的变量属于一个局部变量,并且不会从该方法中逃逸出去,所以我们可以使用锁消除(不加锁)来加速程序的运行。
Atas ialah kandungan terperinci Apakah kaedah pelaksanaan mengunci di Jawa?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!