1.線程安全定義:
百度百科:線程安全是多線程編程時的計算機程序代碼中的一個概念。在擁有共享數(shù)據(jù)的多條線程并行執(zhí)行的程序中,線程安全的代碼會通過同步機制保證各個線程都可以正常且正確的執(zhí)行,不會出現(xiàn)數(shù)據(jù)污染等意外情況。
關(guān)鍵字:數(shù)據(jù)污染
知乎:“線程安全”也不是指線程的安全,而是指內(nèi)存的安全。對應(yīng)的解決方法:1.數(shù)據(jù)不可見,作用域,局部變量,別人訪問不到 2.數(shù)據(jù)隔離,你要拿數(shù)據(jù),只能copy一份走,不能拿走原始數(shù)據(jù) 3.只讀標記,只能讀,不能改,只能看不能摸。 4.互斥鎖,誰先占領(lǐng),就是誰來改 5.樂觀鎖,并發(fā)機會少,場景記錄與驗證,大不了從頭再來。
CSDN: 沒有絕對的安全,只有相對的安全。相對線程安全,牽扯到一些同步調(diào)度處理的問題
2.原子操作的定義:一個不能再被分割的計算機操作,中間不能插入別的操作的操作。
3.線程鎖的定義:表示對資源的占用標志,上了鎖,就保證了內(nèi)存資源的一種占用狀態(tài)。
參考這篇文章介紹:
atomic 只是把讀與寫變成了原子級操作。
nonatomic 是非原子級操作,跟線程安全一點關(guān)系沒有。
文章中的英文翻譯出來就是:
原子的(默認)
原子是默認設(shè)置:如果您不輸入任何內(nèi)容,則您的屬性是原子的。
原子屬性可以保證,如果您嘗試從中讀取內(nèi)容,則將取回有效值。
它不能保證該值是多少,但是您將獲得合法有效的數(shù)據(jù),而不僅僅是垃圾內(nèi)存(又叫臟數(shù)據(jù))。
這允許您執(zhí)行的操作是,如果您有多個線程或多個進程指向一個變量,則一個線程可以讀取而另一個線程可以寫入。
如果它們同時命中,則保證讀取器線程獲得兩個值之一:更改之前或更改之后。
原子不會給您帶來任何保證,您可能會獲得其中哪些值。
原子通常確實與線程安全混淆,這是不正確的。
您需要以其他方式保證線程安全。但是,atomic可以保證,如果您嘗試讀取數(shù)據(jù),則肯定會獲得某種合法數(shù)據(jù)。
非原子
另一方面,您可能會猜到,非原子意味著“不要做原子的事情”。
您所失去的是保證您總是能得到一些回報。
如果嘗試在寫入過程中進行讀取,則可能會獲取垃圾數(shù)據(jù)。
但是,另一方面,您走得更快。
因為原子屬性必須做一些魔術(shù)(遞歸鎖)才能保證您將獲得一個值,所以它們要慢一些。
如果這是您經(jīng)常訪問的屬性,則可能需要降低為非原子屬性,以確保不會造成速度損失。
nonatomic對象setter和getter方法的實現(xiàn):
- (void)setCurrentImage:(UIImage *)currentImage
{
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
- (UIImage *)currentImage
{
return _currentImage;
}
atomic對象setter和getter方法的實現(xiàn):
- (void)setCurrentImage:(UIImage *)currentImage
{
@synchronized(self) {
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
}
- (UIImage *)currentImage
{
@synchronized(self) {
return _currentImage;
}
}
現(xiàn)在我來舉例:
假如有三個異步線程 asyncThreadA,asyncThreadB,asyncThreadC 對一個變量ticketCount 進行操作(注意:這里使用車站售票模型):
asyncThreadA 先讀入票數(shù),再賣出一張票 (讀操作是異步,寫操作也是異步)
asyncThreadB 先讀入票數(shù),再賣出一張票
asyncThreadC 先讀入票數(shù),再賣出一張票
假設(shè)ticketCount票數(shù)初始數(shù)為10,讀是原子操作,寫是原子操作,
那么這三個線程執(zhí)行完了,輸出票數(shù)是多少? 答案是:可能是9,可能是8,可能是7.
9的操作順序可能是: A讀B讀C讀,A寫,B寫,C寫
8的操作順序可能是: A讀B讀A寫C讀,B寫,C寫
7的操作順序可能是: A讀A寫B(tài)讀B寫C讀C寫。
那么問題來:如果只有一個線程去讀去寫,是不是就保證線程安全了呢?
也不對,只有一個線程在執(zhí)行時:也可能發(fā)生A讀B讀C讀,A寫,B寫,C寫的操作。
那么怎么保證線程安全呢: 對買票操作加鎖,這個讀寫對(一讀一寫)操作整體一起加鎖。
順序只能X讀X寫Y讀Y寫。
又有一個問題: 多線程 + 串行隊列,可以保證線程安全嗎?
可以,但是必須將這個讀寫操作對,當成一個整體任務(wù)放入隊列中,這樣才能保證線程安全。
總而言之:單獨一個線程,或者僅僅是串行隊列,都不能完全保證線程安全。
單線程+ 串行隊列 也不能完全保證實現(xiàn)線程安全。
必須加同步限制。
同步操作怎么實現(xiàn):
main(){
operationA();
operationB();
}
這個就是表示同步操作:A代碼執(zhí)行完,才去執(zhí)行B。
實際場景: thread1 讀取數(shù)據(jù),thread2 使用數(shù)據(jù),thread3 寫入數(shù)據(jù)。
如果thread1 讀取數(shù)據(jù)后給thread2發(fā)通知,thread2拿到數(shù)據(jù),使用數(shù)據(jù),在thread2使用數(shù)據(jù)的時候,thread3 在寫入數(shù)據(jù),這個時候如果數(shù)據(jù)都指向同一塊內(nèi)存區(qū)域,那么極大可能會讀入臟數(shù)據(jù)。
如果thread1 讀取數(shù)據(jù)后給thread2發(fā)通知,thread2拿到數(shù)據(jù),使用數(shù)據(jù),在thread2使用數(shù)據(jù)的時候,thread1 又在寫入數(shù)據(jù),這個時候如果數(shù)據(jù)都指向同一塊內(nèi)存區(qū)域,那么極大可能會讀入臟數(shù)據(jù)。
如果thread1 讀取數(shù)據(jù)后給thread2發(fā)通知,thread2拿到數(shù)據(jù),使用數(shù)據(jù),在thread2使用數(shù)據(jù)的時候,thread1 與 thread3都沒有資格對數(shù)據(jù)進行修改,數(shù)據(jù)就安全了。