Java多線(xiàn)程之顯示鎖和內(nèi)置鎖總結(jié)詳解
總結(jié)多線(xiàn)程之顯示鎖和內(nèi)置鎖
Java中具有通過(guò)Synchronized實(shí)現(xiàn)的內(nèi)置鎖,和ReentrantLock實(shí)現(xiàn)的顯示鎖,這兩種鎖各有各的好處,算是互有補(bǔ)充,這篇文章就是做一個(gè)總結(jié)。
*Synchronized*
內(nèi)置鎖獲得鎖和釋放鎖是隱式的,進(jìn)入synchronized修飾的代碼就獲得鎖,走出相應(yīng)的代碼就釋放鎖。
synchronized(list){ //獲得鎖
list.append();
list.count();
}//釋放鎖
通信
與Synchronized配套使用的通信方法通常有wait(),notify()。
wait()方法會(huì)立即釋放當(dāng)前鎖,并進(jìn)入等待狀態(tài),等待到相應(yīng)的notify并重新獲得鎖過(guò)后才能繼續(xù)執(zhí)行;notify()不會(huì)立刻立刻釋放鎖,必須要等notify()所在線(xiàn)程執(zhí)行完synchronized塊中的所有代碼才會(huì)釋放。用如下代碼來(lái)進(jìn)行驗(yàn)證:
public static void main(String[] args){
List list = new LinkedList();
Thread r = new Thread(new ReadList(list));
Thread w = new Thread(new WriteList(list));
r.start();
w.start();
}
class ReadList implements Runnable{
private List list;
public ReadList(List list){
this.list = list;
}
@Override
public void run(){
System.out.println("ReadList begin at "+System.currentTimeMillis());
synchronized (list){
try {
Thread.sleep(1000);
System.out.println("list.wait() begin at "+System.currentTimeMillis());
list.wait();
System.out.println("list.wait() end at "+System.currentTimeMillis());
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("ReadList end at "+System.currentTimeMillis());
}
}
class WriteList implements Runnable{
private List list;
public WriteList(List list){
this.list = list;
}
@Override
public void run(){
System.out.println("WriteList begin at "+System.currentTimeMillis());
synchronized (list){
System.out.println("get lock at "+System.currentTimeMillis());
list.notify();
System.out.println("list.notify() at "+System.currentTimeMillis());
try {
Thread.sleep(2000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("get out of block at "+System.currentTimeMillis());
}
System.out.println("WriteList end at "+System.currentTimeMillis());
}
}
運(yùn)行結(jié)果:
ReadList begin at 1493650526582 WriteList begin at 1493650526582 list.wait() begin at 1493650527584 get lock at 1493650527584 list.notify() at 1493650527584 get out of block at 1493650529584 WriteList end at 1493650529584 list.wait() end at 1493650529584 ReadList end at 1493650529584
可見(jiàn)讀線(xiàn)程開(kāi)始運(yùn)行,開(kāi)始wait過(guò)后,寫(xiě)線(xiàn)程才獲得鎖;寫(xiě)線(xiàn)程走出同步塊而不是notify過(guò)后,讀線(xiàn)程才wait結(jié)束,亦即獲得鎖。所以notify不會(huì)釋放鎖,wait會(huì)釋放鎖。值得一提的是,notifyall()會(huì)通知等待隊(duì)列中的所有線(xiàn)程。
編碼
編碼模式比較簡(jiǎn)單,單一,不必顯示的獲得鎖,釋放鎖,能降低因粗心忘記釋放鎖的錯(cuò)誤。使用模式如下:
synchronized(object){
}
靈活性
1.內(nèi)置鎖在進(jìn)入同步塊時(shí),采取的是無(wú)限等待的策略,一旦開(kāi)始等待,就既不能中斷也不能取消,容易產(chǎn)生饑餓與死鎖的問(wèn)題
2.在線(xiàn)程調(diào)用notify方法時(shí),會(huì)隨機(jī)選擇相應(yīng)對(duì)象的等待隊(duì)列的一個(gè)線(xiàn)程將其喚醒,而不是按照FIFO的方式,如果有強(qiáng)烈的公平性要求,比如FIFO就無(wú)法滿(mǎn)足
性能
Synchronized在JDK1.5及之前性能(主要指吞吐率)比較差,擴(kuò)展性也不如ReentrantLock。但是JDK1.6以后,修改了管理內(nèi)置鎖的算法,使得Synchronized和標(biāo)準(zhǔn)的ReentrantLock性能差別不大。
*ReentrantLock*
ReentrantLock是顯示鎖,需要顯示進(jìn)行 lock 以及 unlock 操作。
通信
與ReentrantLock搭配的通行方式是Condition,如下:
private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); condition.await();//this.wait(); condition.signal();//this.notify(); condition.signalAll();//this.notifyAll();
Condition是被綁定到Lock上的,必須使用lock.newCondition()才能創(chuàng)建一個(gè)Condition。從上面的代碼可以看出,Synchronized能實(shí)現(xiàn)的通信方式,Condition都可以實(shí)現(xiàn),功能類(lèi)似的代碼寫(xiě)在同一行中。而Condition的優(yōu)秀之處在于它可以為多個(gè)線(xiàn)程間建立不同的Condition,比如對(duì)象的讀/寫(xiě)Condition,隊(duì)列的空/滿(mǎn)Condition,在JDK源碼中的ArrayBlockingQueue中就使用了這個(gè)特性:
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
編碼
Lock lock = new ReentrantLock();
lock.lock();
try{
}finally{
lock.unlock();
}
相比于Synchronized要復(fù)雜一些,而且一定要記得在finally中釋放鎖而不是其他地方,這樣才能保證即使出了異常也能釋放鎖。
靈活性
1.lock.lockInterruptibly()可以使得線(xiàn)程在等待鎖是支持響應(yīng)中斷;lock.tryLock()可以使得線(xiàn)程在等待一段時(shí)間過(guò)后如果還未獲得鎖就停止等待而非一直等待。有了這兩種機(jī)制就可以更好的制定獲得鎖的重試機(jī)制,而非盲目一直等待,可以更好的避免饑餓和死鎖問(wèn)題
2.ReentrantLock可以成為公平鎖(非默認(rèn)的),所謂公平鎖就是鎖的等待隊(duì)列的FIFO,不過(guò)公平鎖會(huì)帶來(lái)性能消耗,如果不是必須的不建議使用。這和CPU對(duì)指令進(jìn)行重排序的理由是相似的,如果強(qiáng)行的按照代碼的書(shū)寫(xiě)順序來(lái)執(zhí)行指令,就會(huì)浪費(fèi)許多時(shí)鐘周期,達(dá)不到最大利用率
性能
雖然Synchronized和標(biāo)準(zhǔn)的ReentrantLock性能差別不大,但是ReentrantLock還提供了一種非互斥的讀寫(xiě)鎖,
也就是不強(qiáng)制每次最多只有一個(gè)線(xiàn)程能持有鎖,它會(huì)避免“讀/寫(xiě)”沖突,“寫(xiě)/寫(xiě)”沖突,但是不會(huì)排除“讀/讀”沖突,
因?yàn)椤白x/讀”并不影響數(shù)據(jù)的完整性,所以可以多個(gè)讀線(xiàn)程同時(shí)持有鎖,這樣在讀寫(xiě)比較高的情況下,性能會(huì)有很大的提升。
下面用兩種鎖分別實(shí)現(xiàn)的線(xiàn)程安全的linkedlist:
class RWLockList {
//讀寫(xiě)鎖
private List list;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
public RWLockList(List list){
this.list = list;
}
public int get(int k) {
readLock.lock();
try {
return (int)list.get(k);
}
finally {
readLock.unlock();
}
}
public void put(int value) {
writeLock.lock();
try {
list.add(value);
}
finally {
writeLock.unlock();
}
}
}
class SyncList {
private List list;
public SyncList(List list){
this.list = list;
}
public synchronized int get(int k){
return (int)list.get(k);
}
public synchronized void put(int value){
list.add(value);
}
}
讀寫(xiě)鎖測(cè)試代碼:
List list = new LinkedList();
for (int i=0;i<10000;i++){
list.add(i);
}
RWLockList rwLockList = new RWLockList(list);
//初始化數(shù)據(jù)
Thread writer = new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<10000;i++){
rwLockList.put(i);
}
}
}
);
Thread reader1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<10000;i++){
rwLockList.get(i);
}
}
}
);
Thread reader2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<10000;i++){
rwLockList.get(i);
}
}
}
);
long begin = System.currentTimeMillis();
writer.start();
reader1.start();
reader2.start();
try {
writer.join();
reader1.join();
reader2.join();
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("RWLockList take "+(System.currentTimeMillis()-begin) + "ms");
同步鎖測(cè)試代碼:
List list = new LinkedList();
for (int i=0;i<10000;i++){
list.add(i);
}
SyncList syncList = new SyncList(list);//初始化數(shù)據(jù)
Thread writerS = new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<10000;i++){
syncList.put(i);
}
}
});
Thread reader1S = new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<10000;i++){
syncList.get(i);
}
}
});
Thread reader2S = new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<10000;i++){
syncList.get(i);
}
}
});
long begin1 = System.currentTimeMillis();
writerS.start();reader1S.start();reader2S.start();
try {
writerS.join();
reader1S.join();
reader2S.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("SyncList take "+(System.currentTimeMillis()-begin1) + "ms");
結(jié)果:
RWLockList take 248ms RWLockList take 255ms RWLockList take 249ms RWLockList take 224ms SyncList take 351ms SyncList take 367ms SyncList take 315ms SyncList take 323ms
可見(jiàn)讀寫(xiě)鎖的確是優(yōu)于純碎的互斥鎖
總結(jié)
內(nèi)置鎖最大優(yōu)點(diǎn)是簡(jiǎn)潔易用,顯示鎖最大優(yōu)點(diǎn)是功能豐富,所以能用內(nèi)置鎖就用內(nèi)置鎖,在內(nèi)置鎖功能不能滿(mǎn)足之時(shí)在考慮顯示鎖。
以上就是本文關(guān)于Java多線(xiàn)程之顯示鎖和內(nèi)置鎖總結(jié)詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:
淺談Java多線(xiàn)程的優(yōu)點(diǎn)及代碼示例
Java利用future及時(shí)獲取多線(xiàn)程運(yùn)行結(jié)果
如有不足之處,歡迎留言指出。
- Java多線(xiàn)程并發(fā)編程和鎖原理解析
- Java多線(xiàn)程 ReentrantLock互斥鎖詳解
- Java多線(xiàn)程產(chǎn)生死鎖的必要條件
- Java編程之多線(xiàn)程死鎖與線(xiàn)程間通信簡(jiǎn)單實(shí)現(xiàn)代碼
- Java多線(xiàn)程之死鎖的出現(xiàn)和解決方法
- java多線(xiàn)程學(xué)習(xí)之死鎖的模擬和避免(實(shí)例講解)
- java 多線(xiàn)程死鎖詳解及簡(jiǎn)單實(shí)例
- java 多線(xiàn)程-鎖詳解及示例代碼
- Java多線(xiàn)程鎖機(jī)制相關(guān)原理實(shí)例解析
相關(guān)文章
Spring Boot2開(kāi)發(fā)之Spring Boot整合Shiro兩種詳細(xì)方法
這篇文章主要介紹了Spring Boot2開(kāi)發(fā)之Spring Boot整合Shiro詳細(xì)方法,需要的朋友可以參考下2020-03-03
springboot攔截器過(guò)濾token,并返回結(jié)果及異常處理操作
這篇文章主要介紹了springboot攔截器過(guò)濾token,并返回結(jié)果及異常處理操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
快速解決springboot在yml配置了啟動(dòng)端口但啟動(dòng)還是8080問(wèn)題
這篇文章主要介紹了快速解決springboot在yml配置了啟動(dòng)端口但啟動(dòng)還是8080問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
jdk7 中HashMap的知識(shí)點(diǎn)總結(jié)
HashMap的原理是老生常談了,不作仔細(xì)解說(shuō)。一句話(huà)概括為HashMap是一個(gè)散列表,它存儲(chǔ)的內(nèi)容是鍵值對(duì)(key-value)映射。這篇文章主要總結(jié)了關(guān)于jdk7 中HashMap的知識(shí)點(diǎn),需要的朋友可以參考借鑒,一起來(lái)看看吧。2017-01-01
AQS同步組件CyclicBarrier循環(huán)屏障用例剖析
這篇文章主要為大家介紹了AQS同步組件CyclicBarrier循環(huán)屏障用例剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
SpringMVC @ControllerAdvice使用場(chǎng)景
這篇文章主要介紹了SpringMVC @ControllerAdvice使用場(chǎng)景,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11

