并發(fā)稚失,即一個代碼塊同時被多個線程執(zhí)行搀绣,代碼塊中的變量會被同時的線程修改成不同的值飞袋,造成數據錯亂,運行結果錯誤現(xiàn)象產生链患,如何來避免這一問題產生呢巧鸭?這就產生了鎖機制,通過對代碼塊加鎖麻捻,來保證同一時刻只能有一個線程來操作數據纲仍,這樣就能保證數據的一致性。
一贸毕、鎖機制
- 重入鎖/不可重入鎖
- 共享鎖/互斥鎖
- 樂觀鎖/悲觀鎖
- 自旋鎖
重入鎖/不可重入鎖
在同一個線程中郑叠,可重復進入的鎖,就可重入鎖明棍。如乡革,一個線程中進入了一個帶鎖的方法,再次進入使用同一個鎖的其他方法時摊腋,不需要再獲取鎖沸版,可直接進入。
可重入鎖
- ReentrantLock
- synchronized
不可重入鎖
- NoReentrantLock
共享鎖/互斥鎖
當一個線程獲取到鎖后兴蒸,其他線程也可以獲取此鎖视粮,則此鎖是共享鎖,如讀鎖橙凳;如果其他線程不能獲取此鎖蕾殴,則此鎖是互斥鎖,如寫鎖
- ReentrantReadWriteLock
樂觀鎖/悲觀鎖
樂觀鎖認為并發(fā)操作一定會修改數據痕惋;悲觀鎖認為并發(fā)操作不會修改數據,但在寫數據時一般
樂觀鎖
- AtomickInteger
- AtomickXXX
悲觀鎖
- ReentrantLock
- synchronized
自旋鎖
一般情況下線程在未獲取到鎖的情況下會被阻塞娃殖,而自旋鎖則是內部有一個循環(huán)體在輪詢鎖是否可用值戳。自旋鎖可以減少線程上下文切換帶來的消耗,但會消耗CPU炉爆。
- AtomicInteger
- AtomicXXXX
二堕虹、死鎖
線程都要獲取鎖才能進行運行卧晓,但所有的線程都獲取不到,導致出現(xiàn)所有線程都不能運行的情況赴捞,這種情況就叫作死鎖逼裆。
死鎖場景
1. 設計不當
線程內有多個鎖,一個線程在獲取鎖A后赦政,需要再次獲取鎖B才能執(zhí)行相關代碼胜宇,而此刻另一線程已經獲取到鎖B,但它卻在等待獲取鎖A來執(zhí)行代碼恢着,兩個線程都要獲取對方已經獲取到的鎖桐愉,而又都獲取不到,造成死循環(huán)掰派,也就是死鎖現(xiàn)象从诲。
2. 程序異常
線程在獲取鎖后執(zhí)行相關代碼,執(zhí)行完相關代碼后釋放鎖靡羡。但執(zhí)行過程發(fā)生了異常系洛,導致線程意外退出,獲取到的鎖沒有被釋放略步,導致其他所有線程都無法獲取鎖的情況描扯。
解決方案:添加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可根據加鎖范圍分為以下兩種方式荆烈,但
- 鎖對象
- 鎖類
鎖對象
即針對對象中的方法進行加鎖,一個對象的加鎖方法被線程進入后竟趾,其他線程不能再進入此對象加鎖的方法憔购,但可以進入不同對象的同一加鎖方法。
- synchronized修飾普通方法
public synchronized void test1(){
...
}
- synchronized代碼塊岔帽,內部使用this或成員變量
public void test1(){
synchronized(this){
...
}
}
鎖類
類鎖與對象鎖是一致的玫鸟,但類方法在內存中只有一份。加鎖的是整個類方法犀勒,當一個線程獲取到類方法鎖后屎飘,其他線程不能再進入此方法。
- synchronized修飾靜態(tài)方法
public synchronized static void test1(){
....
}
- synchronized代碼塊贾费,內部使用class
public void test1(){
synchronized(Test.class){
...
}
}
四钦购、鎖機制單例中的應用
單例對并發(fā)方面的考慮主要是
- 防止創(chuàng)建多個對象。單例要求全局只有一個對象
- 防止單例對象中的數據被并發(fā)修改造成錯誤
創(chuàng)建方式
1. 懶漢模式
需要時再創(chuàng)建
class ConnectionManager{
private static ConnectionManager sInstance = new ConnectionManager();
private ConnectionManager(){
}
/**
* 此方式每次獲取都會加鎖褂萧,目的是防止創(chuàng)建多個對象押桃,
* 但創(chuàng)建對象只有一次,其他都是獲取导犹,造成性能損耗
*/
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. 雙重檢測
a. 為什么要2次null判斷羡忘?
防止兩個線程同時進入第一個if模塊,當一個線程獲取鎖后創(chuàng)建一個對象磕昼;在它釋放鎖后卷雕,另一個線程獲取到鎖,又創(chuàng)建一個對象票从。
b. 為什么使用volatile修飾漫雕?
防止指令重排問題。sInstance=new ConnectionManager();這句代碼并不是原子操作纫骑,它有三條指令:分配內存蝎亚、初始化內存、賦值變量先馆。如果先賦值后初始化发框,就可能會出現(xiàn)另一線程在第一個null判斷時,發(fā)現(xiàn)不為空煤墙,直接使用對象的情況梅惯,而此時對象可能還沒被初始化造成錯誤。
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)內部類
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(){
}
}
五仿野、進程通信
- binder
- 管道
- 廣播
- 文件
- socket
生產者消費者機制
信息號機制
六铣减、線程安全相關類
容器類
- ConcurentHashMap
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- LinkedBlockingList
- LinkedBlockingQueue
- Vector
- HashTable
- Collections.synchronizedList(new ArrayList<T>());
- Collections.synchronizedSet(new HashSet<T>());
- Collections.synchronizedMap(new HashMap<String,String>());
字符串類
- StringBuffer