從某個執行緒開始存取到存取結束的整個過程,如果有一個存取物件被其他執行緒修改,那麼對於目前執行緒而言就發生了執行緒安全性問題;如果在整個訪問過程中,無一物件被其他線程修改,就是線程安全的。
#首先是多執行緒環境,也就是同時存在有多個操作者,單線程環境不存在線程安全問題。在單執行緒環境下,任何操作都包含修改操作都是操作者自己發出的,操作者發出操作時不僅有明確的目的,而且意識到操作的影響。
多個操作者(執行緒)必須操作同一個對象,只有多個操作者同時操作一個對象,行為的影響才能立即傳遞到其他操作者。
多個操作者(執行緒)對相同物件的操作必須包含修改操作,共同讀取不存在執行緒安全性問題,因為物件不會修改,未發生變化,不能產生影響。
綜上可知,線程安全問題產生的根本原因是共享資料存在被並發修改的可能,即當一個執行緒讀取時,允許另一個線程修改。
根據線程安全問題產生的條件,解決線程安全問題的思路是消除產生線程安全問題的環境:
消除共享資料:成員變數與靜態變數多執行緒共享,將這些全域變數轉換為局部變量,局部變數存放在棧,線程間不共享,就不存在線程安全問題產生的環境了。消除共享資料的不足:如果需要一個物件來擷取各個執行緒的訊息,或是在執行緒間傳遞訊息,消除了共享物件就無法實現此目的。
使用執行緒同步機制:同時加鎖,使得同時只有一個執行緒可以存取共享資料。如果單單給寫操作加鎖,同時只有一個執行緒可以執行寫入操作,而讀操作不受限制,允許多執行緒並發讀取,這時就可能出現不可重複讀的情況,如一個持續時間比較長的讀線程,相隔較長時間讀取數組同一索引位置的數據,正好在這兩次讀取的時間內,一個線程修改了該索引處的數據,造成該線程從同一索引處前後讀取的數據不一致。是同時為讀寫加鎖,還是只給寫加鎖,根據具體需求而定。同步機制的缺點是降低了程式的吞吐量。
建立副本:使用ThreadLocal為每個執行緒建立一個變數的副本,各個執行緒間獨立操作,互不影響。該方式本質上是消除共享資料思想的一種實現。
#4.可見性#
每個執行緒內部具有共享變數的副本,當一個執行緒更新了這個共享變量,另一個執行緒可能看的到,可能看不到,這就是可見性問題,以下面的程式碼為例:
public class NoVisibility { private static boolean ready; private static int number; public static class ReadThread extends Thread { public void run() { while(!ready ) Thread. yield(); System. out.println(number); } } public static void main(String [] args) { new ReadThread().start(); number = 42; ready = true ; } }
# 以上程式碼可能輸出0或什麼也不能輸出。為什麼會什麼也不能輸出呢?因為我們在主執行緒中把ready置為true,但是ReadThread中卻不一定能夠讀到我們設定的ready值,所以在ReadThread中Thread.yield()會一直執行下去。為什麼可能為0呢?如果ReadThread能夠讀到我們的值,可能先讀到ready值為true,還未讀取更新number值,ReadThread就把保有的number值輸出了,也就是0。
注意,上述的所有內容都是假設,在缺乏同步的情況下,ReadThread和主執行緒會如何交互,我們是無法預期的,以上兩種情況只是兩種可能性。那麼該如何避免這種問題呢?很簡單,只要有資料在多個執行緒之間共享,就使用正確的同步。
4.1、加鎖與可見性
内置锁可以用于确保某个线程以一种可预测的方式查看另一个线程的执行结果,当线程A进入某同步代码块时,线程B随后进入由同一个锁保护的同步代码块,此时,线程B执行由锁保护的同步代码块时,可以看到线程A之前在同一同步代码块中的所有操作结果,如果没有同步,那么就无法实现上述保证。
加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。
4.2、volatile变量
volatile是一种比synchronized关键字轻量级的同步机制,volatile关键字可以确保变量的更新操作通知到其他线程。
下面是volatile的典型用法:
volatile boolean asleep; ... while(!asleep) doSomeThing();
加锁机制既可以确保可见性,又可以确保原子性,而volatile变量只能确保可见性。
5、总结
编写线程安全的代码,其核心在于要对状态访问操作进行管理。编写线程安全的代码时,有两个关注点,一个是原子性问题,一个是可见性问题,要尽量避免竞态条件错误。
以上是java有關多線程性安全性問題的原理講解的詳細內容。更多資訊請關注PHP中文網其他相關文章!