1.在java中,每一個(gè)對(duì)象都擁有一個(gè)鎖標(biāo)記(monitor),也成為監(jiān)視器,多個(gè)線程同時(shí)訪問某個(gè)對(duì)象時(shí),線程只有獲取了該對(duì)象的鎖才能訪問。
2.當(dāng)使用synchronized關(guān)鍵字來(lái)標(biāo)記一個(gè)方法或代碼塊,當(dāng)線程調(diào)用改方法或代碼塊時(shí),這個(gè)線程便獲得了該對(duì)象的鎖。
3.當(dāng)一個(gè)線程正在訪問一個(gè)對(duì)象的synchronized方法時(shí),那么其他線程不能訪問該對(duì)象的其他synchronized方法。因?yàn)橐粋€(gè)對(duì)象只有一把鎖,當(dāng)一個(gè)線程獲取了該對(duì)象的鎖后,其他線程就無(wú)法獲取該對(duì)象的鎖,所以無(wú)法訪問該對(duì)象的其他synchronized方法。
下面例子中的method和method2就無(wú)法同時(shí)訪問
package com.demo;
public class SynchronizedTest {
class Test{
public synchronized void method1() throws InterruptedException{
System.out.println("visiting method1");
Thread.sleep(5000);
}
public synchronized void method2() throws InterruptedException{
System.out.println("visiting method2");
Thread.sleep(5000);
}
}
public Test getTestInstance(){
return new Test();
}
public static void main(String[] args) throws Exception{
SynchronizedTest synchronizedTest = new SynchronizedTest();
Test test = synchronizedTest.getTestInstance();
new Thread( ()-> {try {
test.method1();
} catch (InterruptedException e) {
e.printStackTrace();
}}).start();
new Thread( ()-> {try {
test.method2();
} catch (InterruptedException e) {
e.printStackTrace();
}}).start();
}
}
4.每個(gè)類也會(huì)有一把鎖,用來(lái)控制對(duì)static數(shù)據(jù)成員的并發(fā)訪問。如果一個(gè)線程執(zhí)行一個(gè)對(duì)象的非static synchronized方法,另一個(gè)線程需要執(zhí)行這個(gè)對(duì)象所屬類的static synchronized方法,此時(shí)不會(huì)發(fā)生互斥現(xiàn)象。synchronized的類鎖都是通過synchronized(this)來(lái)加鎖。
5.線程安全問題:當(dāng)多個(gè)線程訪問一個(gè)資源時(shí),會(huì)導(dǎo)致程序運(yùn)行結(jié)果不是想要看到的結(jié)果。
6.如何解決線程安全問題的呢?
基本上所有的并發(fā)模式在解決線程安全問題時(shí),都采用“序列化訪問臨界資源”的方案,即在同一時(shí)刻,只能有一個(gè)線程訪問臨界資源,也稱作同步互斥訪問。
7.對(duì)于synchronized方法或者synchronized代碼塊,當(dāng)出現(xiàn)異常時(shí),jvm會(huì)自動(dòng)釋放當(dāng)前線程占用的鎖,因此不會(huì)因?yàn)榫€程導(dǎo)致死鎖。
8.synchronized的缺陷:
(1)當(dāng)一個(gè)線程獲得synchronized鎖時(shí),當(dāng)它發(fā)生阻塞時(shí),不會(huì)釋放鎖,其他線程想要獲取這個(gè)synchronized鎖,只能一直阻塞等待上一個(gè)鎖釋放。
(2)通過synchronized無(wú)法知道是否成功獲取到鎖。
(3)synchorinzed當(dāng)有多個(gè)讀操作時(shí),一個(gè)讀操作獲得鎖后,其他讀操作鎖只能阻塞等待。
9.Lock是一個(gè)接口:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
10.lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用來(lái)獲取鎖的。unLock()方法是用來(lái)釋放鎖的。
11.采用Lock,必須主動(dòng)去釋放鎖,并且在發(fā)生異常時(shí),不會(huì)自動(dòng)釋放鎖。因此一般來(lái)說,使用Lock必須在try{}catch{}塊中進(jìn)行,并且將釋放鎖的操作放在finally塊中進(jìn)行,以保證鎖一定被被釋放,防止死鎖的發(fā)生。
Lock lock = ...;
lock.lock();
try{
//處理任務(wù)
}catch(Exception ex){
}finally{
lock.unlock(); //釋放鎖
}
12. tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區(qū)別在于這個(gè)方法在拿不到鎖時(shí)會(huì)等待一定的時(shí)間,在時(shí)間期限之內(nèi)如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內(nèi)拿到了鎖,則返回true。
13.ReentrantLock,意思是“可重入鎖”,ReentrantLock是唯一實(shí)現(xiàn)了Lock接口的類,并且ReentrantLock提供了更多的方法。
14.ReadWriteLock也是一個(gè)接口,在它里面只定義了兩個(gè)方法
public interface ReadWriteLock {
/**
* @return the lock used for reading.
*/
Lock readLock();
/**
* @return the lock used for writing.
*/
Lock writeLock();
}
15.ReentrantReadWriteLock里面提供了很多豐富的方法,不過最主要的有兩個(gè)方法:readLock()和writeLock()用來(lái)獲取讀鎖和寫鎖
//一般命名為成員變量,否則在方法中,會(huì)出現(xiàn)一個(gè)工作內(nèi)存會(huì)有一個(gè)鎖
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.readLock().lock();
rwl.writeLock().lock();
16.Lock和synchronized有以下幾點(diǎn)不同:
1)Lock是一個(gè)接口,而synchronized是Java中的關(guān)鍵字,synchronized是內(nèi)置的語(yǔ)言實(shí)現(xiàn);
2)synchronized在發(fā)生異常時(shí),會(huì)自動(dòng)釋放線程占有的鎖,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時(shí),如果沒有主動(dòng)通過unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用Lock時(shí)需要在finally塊中釋放鎖;
3)Lock可以讓等待鎖的線程響應(yīng)中斷,而synchronized卻不行,使用synchronized時(shí),等待的線程會(huì)一直等待下去,不能夠響應(yīng)中斷;
4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無(wú)法辦到。
5)Lock可以提高多個(gè)線程進(jìn)行讀操作的效率。
17.鎖相關(guān)的幾個(gè)概念
(1)可重入鎖:如果鎖具備可重入性,則稱作為可重入鎖。一個(gè)線程能擁有一把鎖,當(dāng)線程獲得鎖后,相當(dāng)于拿到了這把鎖的鑰匙,可以用這把鑰匙去重復(fù)開鎖。
class MyClass {
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}
上述代碼中的兩個(gè)方法method1和method2都用synchronized修飾了,假如某一時(shí)刻,線程A執(zhí)行到了method1,此時(shí)線程A獲取了這個(gè)對(duì)象的鎖,而由于method2也是synchronized方法,假如synchronized不具備可重入性,此時(shí)線程A需要重新申請(qǐng)鎖。但是這就會(huì)造成一個(gè)問題,因?yàn)榫€程A已經(jīng)持有了該對(duì)象的鎖,而又在申請(qǐng)獲取該對(duì)象的鎖,這樣就會(huì)線程A一直等待永遠(yuǎn)不會(huì)獲取到的鎖。
而由于synchronized和Lock都具備可重入性,所以不會(huì)發(fā)生上述現(xiàn)象。
(2)可中斷鎖:顧名思義,就是可以相應(yīng)中斷的鎖。
在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。
如果某一線程A正在執(zhí)行鎖中的代碼,另一線程B正在等待獲取該鎖,可能由于等待時(shí)間過長(zhǎng),線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,這種就是可中斷鎖。
(3)公平鎖
公平鎖即盡量以請(qǐng)求鎖的順序來(lái)獲取鎖。比如同是有多個(gè)線程在等待一個(gè)鎖,當(dāng)這個(gè)鎖被釋放時(shí),等待時(shí)間最久的線程(最先請(qǐng)求的線程)會(huì)獲得該所,這種就是公平鎖。
非公平鎖即無(wú)法保證鎖的獲取是按照請(qǐng)求鎖的順序進(jìn)行的。這樣就可能導(dǎo)致某個(gè)或者一些線程永遠(yuǎn)獲取不到鎖。
在Java中,synchronized就是非公平鎖,它無(wú)法保證等待的線程獲取鎖的順序。
而對(duì)于ReentrantLock和ReentrantReadWriteLock,它默認(rèn)情況下是非公平鎖,但是可以設(shè)置為公平鎖。
(4)讀寫鎖
讀寫鎖將對(duì)一個(gè)資源(比如文件)的訪問分成了2個(gè)鎖,一個(gè)讀鎖和一個(gè)寫鎖。
正因?yàn)橛辛俗x寫鎖,才使得多個(gè)線程之間的讀操作不會(huì)發(fā)生沖突。
ReadWriteLock就是讀寫鎖,它是一個(gè)接口,ReentrantReadWriteLock實(shí)現(xiàn)了這個(gè)接口。
可以通過readLock()獲取讀鎖,通過writeLock()獲取寫鎖。