當多個線程去訪問某個類時,若是類會表現出咱們預期出現的行爲,那麼能夠稱這個類是線程安全的。html
操做並不是原子。多個線程執行某段代碼,若是這段代碼產生的結果受不一樣線程之間的執行時序影響,而產生非預期的結果,即發生了競態條件,就會出現線程不安全;java
常見場景:安全
count++
。它自己包含三個操做,讀取、修改、寫入,多線程時,因爲線程執行的時序不一樣,有可能致使兩個線程執行後count只加了1,而原有的目標確實但願每次執行都加1;- 單例。多個線程可能同時執行到
instance == null
成立,而後新建了兩個對象,而原有目標是但願這個對象永遠只有一個;public MyObj getInstance(){ if (instance == null){ instance = new MyObj(); } return instance } 複製代碼
解決方式是:當前線程在操做這段代碼時,其它線程不能對進行操做bash
常見方案:多線程
- 單個狀態使用 java.util.concurrent.atomic包中的一些原子變量類,注意若是是多個狀態就算每一個操做是原子的,複合使用的時候並非原子的;
- 加鎖。好比使用 synchronized 包圍對應代碼塊,保證多線程之間是互斥的,注意應儘量的只包含在須要做爲原子處理的代碼塊上;
synchronized的可重入性
當線程要去獲取它本身已經持有的鎖是會成功的,這樣的鎖是可重入的,synchronized是可重入的oracle
class Paxi { public synchronized void sayHello(){ System.out.println("hello"); } } class MyClass extends Paxi{ public synchronized void dosomething(){ System.out.println("do thing .."); super.sayHello(); System.out.println("over"); } } 複製代碼
它的輸出爲dom
do thing .. hello over 複製代碼
修改不可見。讀線程沒法感知到其它線程寫入的值ui
常見場景:this
- 重排序。在沒有同步的狀況下,編譯器、處理器以及運行時等都有可能對操做的執行順序進行調整,即寫的代碼順序和真正的執行順序不同,致使讀到的是一個失效的值
- 讀取long、double等類型的變量。JVM容許將一個64位的操做分解成兩個32位的操做,讀寫在不一樣的線程中時,可能讀到錯誤的高低位組合
常見方案:atom
- 加鎖。全部線程都能看到共享變量的最新值;
- 使用Volatile關鍵字聲明變量。只要對這個變量產生了寫操做,那麼全部的讀操做都會看到這個修改;
注意:Volatile並不能保證操做的原子性,好比
count++
操做一樣有風險,它僅保證讀取時返回最新的值。使用的好處在於訪問Volatile變量並不會執行加鎖操做,也就不會阻塞線程。
讀取-修改-寫入
使用final去修飾字段,這樣這個字段的「值」是不可改變的
注意final若是修飾的是一個對象引用,好比set,它自己包含的值是可變的
建立一個不可變的類,來包含多個可變的數據。
class OneValue{
//建立不可變對象,建立以後沒法修改,事實上這裏也沒有提供修改的方法
private final BigInteger last;
private final BigInteger[] lastfactor;
public OneValue(BigInteger i,BigInteger[] lastfactor){
this.last=i;
this.lastfactor=Arrays.copy(lastfactor,lastfactor.length);
}
public BigInteger[] getF(BigInteger i){
if(last==null || !last.equals(i)){
return null;
}else{
return Arrays.copy(lastfactor,lastfactor.length)
}
}
}
class MyService {
//volatile使得cache一經更改,就能被全部線程感知到
private volatile OneValue cache=new OneValue(null,null);
public void handle(BigInteger i){
BigInteger[] lastfactor=cache.getF(i);
if(lastfactor==null){
lastfactor=factor(i);
//每次都封裝最新的值
cache=new OneValue(i,lastfactor)
}
nextHandle(lastfactor)
}
}
複製代碼
實例封閉。將一個對象封裝到另外一個對象中,這樣可以訪問被封裝對象的全部代碼路徑都是已知的,經過合適的加鎖策略能夠確保被封裝對象的訪問是線程安全的。
java中的Collections.synchronizedList使用的原理就是這樣。部分代碼爲
public static <T> List<T> synchronizedList(List<T> list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list)); } 複製代碼
SynchronizedList的實現,注意此處用到的mutex是內置鎖
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
}
複製代碼
mutex的實現
static class SynchronizedCollection<E> implements Collection<E>, >Serializable {
private static final long serialVersionUID = 3053995032091335093L;
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c) {
if (c==null)
throw new NullPointerException();
this.c = c;
mutex = this; // mutex實際上就是對象自己
}
複製代碼
java的監視器模式,將對象全部可變狀態都封裝起來,並由對象本身的內置鎖來保護,便是一種實例封閉。好比HashTable就是運用的監視器模式。它的get操做就是用的synchronized,內置鎖,來實現的線程安全
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
複製代碼
內置鎖
每一個對象都有內置鎖。內置鎖也稱爲監視器鎖。或者能夠簡稱爲監視器
線程執行一個對象的用synchronized修飾的方法時,會自動的獲取這個對象的內置鎖,方法返回時自動釋放內置鎖,執行過程當中就算拋出異常也會自動釋放。
如下兩種寫法等效:
synchronized void myMethdo(){
//do something
}
void myMethdo(){
synchronized(this){
//do somthding
}
}
複製代碼
私有鎖
public class PrivateLock{
private Object mylock = new Object(); //私有鎖
void myMethod(){
synchronized(mylock){
//do something
}
}
}
複製代碼
它也能夠用來保護對象,相對內置鎖,優點在於私有鎖能夠有多個,同時可讓客戶端代碼顯示的獲取私有鎖
類鎖
在staic方法上修飾的,一個類的全部對象共用一把鎖
把線程安全性委託給線程安全的類
視狀況而定。
只有單個組件,且它是線程安全的。
public class DVT{
private final ConcurrentMap<String,Point> locations;
private final Map<String,Point> unmodifiableMap;
public DVT(Map<String,Point> points){
locations=new ConcurrentHashMap<String,Point>(points);
unmodifiableMap=Collections.unmodifiableMap(locations);
}
public Map<String,Point> getLocations(){
return unmodifiableMap;
}
public Point getLocation(String id){
return locations.get(id);
}
public void setLocation(String id,int x,int y){
if(locations.replace(id,new Point(x,y))==null){
throw new IllegalArgumentException("invalid "+id);
}
}
}
public class Point{
public final int x,y;
public Point(int x,int y){
this.x=x;
this.y=y;
}
}
複製代碼
線程安全性分析
綜上,DVT的安全交給了‘locations’,它自己是線程安全的,DVT自己雖沒有任何顯示的同步,也是線程安全。這種狀況下,就是DVT的線程安全實際是委託給了‘locations’,整個DVT表現出了線程安全。
線程安全性委託給了多個狀態變量
只要多個狀態變量之間彼此獨立,組合的類並不會在其包含的多個狀態變量上增長不變性。依賴的增長則沒法保證線程安全
public class NumberRange{
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i){
//先檢查後執行,存在隱患
if (i>upper.get(i)){
throw new IllegalArgumentException('can not ..');
}
lower.set(i);
}
public void setUpper(int i){
//先檢查後執行,存在隱患
if(i<lower.get(i)){
throw new IllegalArgumentException('can not ..');
}
upper.set(i);
}
}
複製代碼
setLower和setUpper都是‘先檢查後執行’的操做,可是沒有足夠的加鎖機制保證操做的原子性。假設原始範圍是(0,10),一個線程調用 setLower(5),一個設置setUpper(4)錯誤的執行時序將可能致使結果爲(5,4)
假設須要擴展的功能爲 ‘沒有就添加’。
public class ListHelper<E>{
public List<E> list=Collections.synchronizedList(new ArrayList<E>());
...
public synchronized boolean putIfAbsent(E x){
boolean absent = !list.contains(x);
if(absent){
list.add(x);
}
return absent;
}
}
複製代碼
這裏的putIfAbsent並不能帶來線程安全,緣由是list的內置鎖並非ListHelper,也就是putIfAbsent相對list的其它方法並非原子的。Collections.synchronizedList是鎖在list自己的,正確方式爲public boolean putIfAbsent(E x){
synchronized(list){
boolean absent = !list.contains(x);
if(absent){
list.add(x);
}
return absent;
}
}
複製代碼
另外能夠無論要操做的類是不是線程安全,對類統一添加一層額外的鎖。 實現參考Collections.synchronizedList方法