并發(fā),即一個(gè)代碼塊同時(shí)被多個(gè)線程執(zhí)行,代碼塊中的變量會(huì)被同時(shí)的線程修改成不同的值,造成數(shù)據(jù)錯(cuò)亂,運(yùn)行結(jié)果錯(cuò)誤現(xiàn)象產(chǎn)生,如何來避免這一問題產(chǎn)生呢?這就產(chǎn)生了鎖機(jī)制,通過對(duì)代碼塊加鎖,來保證同一時(shí)刻只能有一個(gè)線程來操作數(shù)據(jù),這樣就能保證數(shù)據(jù)的一致性。
一、鎖機(jī)制
- 重入鎖/不可重入鎖
- 共享鎖/互斥鎖
- 樂觀鎖/悲觀鎖
- 自旋鎖
重入鎖/不可重入鎖
在同一個(gè)線程中,可重復(fù)進(jìn)入的鎖,就可重入鎖。如,一個(gè)線程中進(jìn)入了一個(gè)帶鎖的方法,再次進(jìn)入使用同一個(gè)鎖的其他方法時(shí),不需要再獲取鎖,可直接進(jìn)入。
可重入鎖
- ReentrantLock
- synchronized
不可重入鎖
- NoReentrantLock
共享鎖/互斥鎖
當(dāng)一個(gè)線程獲取到鎖后,其他線程也可以獲取此鎖,則此鎖是共享鎖,如讀鎖;如果其他線程不能獲取此鎖,則此鎖是互斥鎖,如寫鎖
- ReentrantReadWriteLock
樂觀鎖/悲觀鎖
樂觀鎖認(rèn)為并發(fā)操作一定會(huì)修改數(shù)據(jù);悲觀鎖認(rèn)為并發(fā)操作不會(huì)修改數(shù)據(jù),但在寫數(shù)據(jù)時(shí)一般
樂觀鎖
- AtomickInteger
- AtomickXXX
悲觀鎖
- ReentrantLock
- synchronized
自旋鎖
一般情況下線程在未獲取到鎖的情況下會(huì)被阻塞,而自旋鎖則是內(nèi)部有一個(gè)循環(huán)體在輪詢鎖是否可用。自旋鎖可以減少線程上下文切換帶來的消耗,但會(huì)消耗CPU。
- AtomicInteger
- AtomicXXXX
二、死鎖
線程都要獲取鎖才能進(jìn)行運(yùn)行,但所有的線程都獲取不到,導(dǎo)致出現(xiàn)所有線程都不能運(yùn)行的情況,這種情況就叫作死鎖。
死鎖場(chǎng)景
1. 設(shè)計(jì)不當(dāng)
線程內(nèi)有多個(gè)鎖,一個(gè)線程在獲取鎖A后,需要再次獲取鎖B才能執(zhí)行相關(guān)代碼,而此刻另一線程已經(jīng)獲取到鎖B,但它卻在等待獲取鎖A來執(zhí)行代碼,兩個(gè)線程都要獲取對(duì)方已經(jīng)獲取到的鎖,而又都獲取不到,造成死循環(huán),也就是死鎖現(xiàn)象。
2. 程序異常
線程在獲取鎖后執(zhí)行相關(guān)代碼,執(zhí)行完相關(guān)代碼后釋放鎖。但執(zhí)行過程發(fā)生了異常,導(dǎo)致線程意外退出,獲取到的鎖沒有被釋放,導(dǎo)致其他所有線程都無法獲取鎖的情況。
解決方案:添加try/catch,在finally塊里釋放鎖
class AClass{
ReentrantLock lock=new ReentrantLock();
void aMethod() {
lock.lock();
try {
doSomeThing();
} catch(Throwable e){
e.printStacktrace();
} finally {
lock.unlock();
}
}
}
三、synchronized使用方式
synchronized可根據(jù)加鎖范圍分為以下兩種方式,但
- 鎖對(duì)象
- 鎖類
鎖對(duì)象
即針對(duì)對(duì)象中的方法進(jìn)行加鎖,一個(gè)對(duì)象的加鎖方法被線程進(jìn)入后,其他線程不能再進(jìn)入此對(duì)象加鎖的方法,但可以進(jìn)入不同對(duì)象的同一加鎖方法。
- synchronized修飾普通方法
public synchronized void test1(){
...
}
- synchronized代碼塊,內(nèi)部使用this或成員變量
public void test1(){
synchronized(this){
...
}
}
鎖類
類鎖與對(duì)象鎖是一致的,但類方法在內(nèi)存中只有一份。加鎖的是整個(gè)類方法,當(dāng)一個(gè)線程獲取到類方法鎖后,其他線程不能再進(jìn)入此方法。
- synchronized修飾靜態(tài)方法
public synchronized static void test1(){
....
}
- synchronized代碼塊,內(nèi)部使用class
public void test1(){
synchronized(Test.class){
...
}
}
四、鎖機(jī)制單例中的應(yīng)用
單例對(duì)并發(fā)方面的考慮主要是
- 防止創(chuàng)建多個(gè)對(duì)象。單例要求全局只有一個(gè)對(duì)象
- 防止單例對(duì)象中的數(shù)據(jù)被并發(fā)修改造成錯(cuò)誤
創(chuàng)建方式
1. 懶漢模式
需要時(shí)再創(chuàng)建
class ConnectionManager{
private static ConnectionManager sInstance = new ConnectionManager();
private ConnectionManager(){
}
/**
* 此方式每次獲取都會(huì)加鎖,目的是防止創(chuàng)建多個(gè)對(duì)象,
* 但創(chuàng)建對(duì)象只有一次,其他都是獲取,造成性能損耗
*/
public synchronized static ConnectionManager getInstance(){
if(sInstance==null){
sInstance=new ConnectionManager();
}
return sInstance;
}
}
2. 餓漢模式
直接初始化(太饑餓,上來就直接吃)
public class ConnectionManager{
private static ConnectionManager sInstance = new ConnectionManager();
private ConnectionManager(){
}
public static ConnectionManager getInstance(){
return sInstance;
}
}
3. 雙重檢測(cè)
a. 為什么要2次null判斷?
防止兩個(gè)線程同時(shí)進(jìn)入第一個(gè)if模塊,當(dāng)一個(gè)線程獲取鎖后創(chuàng)建一個(gè)對(duì)象;在它釋放鎖后,另一個(gè)線程獲取到鎖,又創(chuàng)建一個(gè)對(duì)象。
b. 為什么使用volatile修飾?
防止指令重排問題。sInstance=new ConnectionManager();這句代碼并不是原子操作,它有三條指令:分配內(nèi)存、初始化內(nèi)存、賦值變量。如果先賦值后初始化,就可能會(huì)出現(xiàn)另一線程在第一個(gè)null判斷時(shí),發(fā)現(xiàn)不為空,直接使用對(duì)象的情況,而此時(shí)對(duì)象可能還沒被初始化造成錯(cuò)誤。
class ConnectionManager{
private volatile static ConnectionManager sInstance = null;
private ConnectionManager(){
}
public static ConnectionManager getInstance(){
if(sInstance==null){
synchronized(ConnectionManager.class){
if(sInstance==null){
sInstance=new ConnectionManager();
}
}
}
return sInstance;
}
}
4. 靜態(tài)內(nèi)部類
class ConnectionManager{
private ConnectionManager(){
}
public static ConnectionManager getInstance(){
return Holder.INSTANCE;
}
private static class Holder{
private final static ConnectionManager INSTANCE = new ConnectionManager();
}
}
5. 枚舉
enum ConnectionManager{
INSTANCE;
private ConnectionManager(){
}
}
五、進(jìn)程通信
- binder
- 管道
- 廣播
- 文件
- socket
生產(chǎn)者消費(fèi)者機(jī)制
信息號(hào)機(jī)制
六、線程安全相關(guān)類
容器類
- ConcurentHashMap
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- LinkedBlockingList
- LinkedBlockingQueue
- Vector
- HashTable
- Collections.synchronizedList(new ArrayList<T>());
- Collections.synchronizedSet(new HashSet<T>());
- Collections.synchronizedMap(new HashMap<String,String>());
字符串類
- StringBuffer