onStart()是activity界面被顯示出來的時候執行的,但不能與它交互; onResume()是當該activity與用戶能進行交互時被執行,用戶能夠得到activity的焦點,可以與用戶交互。html
startActivity最終都會調用startActivityForResult,經過ActivityManagerProxy調用system_server進程中ActivityManagerService的startActvity方法,若是須要啓動的Activity所在進程未啓動,則調用Zygote孵化應用進程,進程建立後會調用應用的ActivityThread的main方法,main方法調用attach方法將應用進程綁定到ActivityManagerService(保存應用的ApplicationThread的代理對象)並開啓loop循環接收消息。ActivityManagerService經過ApplicationThread的代理髮送Message通知啓動Activity,ActivityThread內部Handler處理handleLaunchActivity,依次調用performLaunchActivity,handleResumeActivity(即activity的onCreate,onStart,onResume)。
深刻理解Activity啓動流程java
Android平臺上虛擬機運行的是Dex字節碼,一種對class文件優化的產物,傳統Class文件是一個Java源碼文件會生成一個.class文件,而Android是把全部Class文件進行合併,優化,而後生成一個最終的class.dex,目的是把不一樣class文件重複的東西只需保留一份,若是咱們的Android應用不進行分dex處理,最後一個應用的apk只會有一個dex文件。 Android中經常使用的有兩種類加載器,DexClassLoader和PathClassLoader,它們都繼承於BaseDexClassLoader。區別在於調用父類構造器時,DexClassLoader多傳了一個optimizedDirectory參數,這個目錄必須是內部存儲路徑,用來緩存系統建立的Dex文件。而PathClassLoader該參數爲null,只能加載內部存儲目錄的Dex文件。因此咱們能夠用DexClassLoader去加載外部的apk。android
Android是基於事件驅動的,即全部Activity的生命週期都是經過Handler事件驅動的。loop方法中會調用MessageQueue的next方法獲取下一個message,當沒有消息時,基於Linux pipe/epoll機制會阻塞在loop的queue.next()中的nativePollOnce()方法裏,並不會消耗CPU。git
IdleHandler是一個回調接口,能夠經過MessageQueue的addIdleHandler添加實現類。當MessageQueue中的任務暫時處理完了(沒有新任務或者下一個任務延時在以後),這個時候會回調這個接口,返回false,那麼就會移除它,返回true就會在下次message處理完了的時候繼續回調。github
同步屏障能夠經過MessageQueue.postSyncBarrier函數來設置。該方法發送了一個沒有target的Message到Queue中,在next方法中獲取消息時,若是發現沒有target的Message,則在必定的時間內跳過同步消息,優先執行異步消息。再換句話說,同步屏障爲Handler消息機制增長了一種簡單的優先級機制,異步消息的優先級要高於同步消息。在建立Handler時有一個async參數,傳true表示此handler發送的時異步消息。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保證UI繪製優先執行。web
View的繪製從ActivityThread類中Handler的處理RESUME_ACTIVITY事件開始,在執行performResumeActivity以後,建立Window以及DecorView並調用WindowManager的addView方法添加到屏幕上,addView又調用ViewRootImpl的setView方法,最終執行performTraversals方法,依次執行performMeasure,performLayout,performDraw。也就是view繪製的三大過程。
measure過程測量view的視圖大小,最終須要調用setMeasuredDimension方法設置測量的結果,若是是ViewGroup須要調用measureChildren或者measureChild方法進而計算本身的大小。
layout過程是擺放view的過程,View不須要實現,一般由ViewGroup實現,在實現onLayout時能夠經過getMeasuredWidth等方法獲取measure過程測量的結果進行擺放。 draw過程先是繪製背景,其次調用onDraw()方法繪製view的內容,再而後調用dispatchDraw()調用子view的draw方法,最後繪製滾動條。ViewGroup默認不會執行onDraw方法,若是複寫了onDraw(Canvas)方法,須要調用 setWillNotDraw(false);清楚不須要繪製的標記。
Android視圖繪製流程徹底解析,帶你一步步深刻了解View(二)算法
MeasureSpec表明一個32位int值,高兩位表明SpecMode(測量模式),低30位表明SpecSize(具體大小)。 SpecMode有三類:json
首先getMeasureWidth()方法在measure()過程結束後就能夠獲取到了,而getWidth()方法要在layout()過程結束後才能獲取到。另外,getMeasureWidth()方法中的值是經過setMeasuredDimension()方法來進行設置的,而getWidth()方法中的值則是經過視圖右邊的座標減去左邊的座標計算出來的。數組
相同點:三個方法都有刷新界面的效果。 不一樣點:invalidate和postInvalidate只會調用onDraw()方法;requestLayout則會從新調用onMeasure、onLayout、onDraw。
調用了invalidate方法後,會爲該View添加一個標記位,同時不斷向父容器請求刷新,父容器經過計算得出自身須要重繪的區域,直到傳遞到ViewRootImpl中,最終觸發performTraversals方法,進行開始View樹重繪流程(只繪製須要重繪的視圖)。
調用requestLayout方法,會標記當前View及父容器,同時逐層向上提交,直到ViewRootImpl處理該事件,ViewRootImpl會調用三大流程,從measure開始,對於每個含有標記位的view及其子View都會進行測量onMeasure、佈局onLayout、繪製onDraw。
Android View 深度分析requestLayout、invalidate與postInvalidate
爲何使用Binder?
概念 進程隔離 進程空間劃分:用戶空間(User Space)/內核空間(Kernel Space) 系統調用:用戶態與內核態
原理 跨進程通訊是須要內核空間作支持的。傳統的 IPC 機制如管道、Socket 都是內核的一部分,所以經過內核支持來實現進程間通訊天然是沒問題的。可是 Binder 並非 Linux 系統內核的一部分,那怎麼辦呢?這就得益於 Linux 的動態內核可加載模塊(Loadable Kernel Module,LKM)的機制;模塊是具備獨立功能的程序,它能夠被單獨編譯,可是不能獨立運行。它在運行時被連接到內核做爲內核的一部分運行。這樣,Android 系統就能夠經過動態添加一個內核模塊運行在內核空間,用戶進程之間經過這個內核模塊做爲橋樑來實現通訊。
在 Android 系統中,這個運行在內核空間,負責各個用戶進程經過 Binder 實現通訊的內核模塊就叫 Binder 驅動(Binder Dirver)。
那麼在 Android 系統中用戶進程之間是如何經過這個內核模塊(Binder 驅動)來實現通訊的呢?難道是和前面說的傳統 IPC 機制同樣,先將數據從發送方進程拷貝到內核緩存區,而後再將數據從內核緩存區拷貝到接收方進程,經過兩次拷貝來實現嗎?顯然不是,不然也不會有開篇所說的 Binder 在性能方面的優點了。
這就不得不通道 Linux 下的另外一個概念:內存映射。
Binder IPC 機制中涉及到的內存映射經過 mmap() 來實現,mmap() 是操做系統中一種內存映射的方法。內存映射簡單的講就是將用戶空間的一塊內存區域映射到內核空間。映射關係創建後,用戶對這塊內存區域的修改能夠直接反應到內核空間;反以內核空間對這段區域的修改也能直接反應到用戶空間。
一次完整的 Binder IPC 通訊過程一般是這樣:
Binder通信模型 Binder是基於C/S架構的,其中定義了4個角色:Client、Server、Binder驅動和ServiceManager。
ServiceManager是一個單獨的進程,那麼Server與ServiceManager通信是靠什麼呢? 當Android系統啓動後,會建立一個名稱爲servicemanager的進程,這個進程經過一個約定的命令BINDERSETCONTEXT_MGR向Binder驅動註冊,申請成爲爲ServiceManager,Binder驅動會自動爲ServiceManager建立一個Binder實體。而且這個Binder實體的引用在全部的Client中都爲0,也就說各個Client經過這個0號引用就能夠和ServiceManager進行通訊。Server經過0號引用向ServiceManager進行註冊,Client經過0號引用就能夠獲取到要通訊的Server的Binder引用。 寫給 Android 應用工程師的 Binder 原理剖析
一篇文章瞭解相見恨晚的 Android Binder 進程間通信機制
Serializable是Java提供的一個序列化接口,是一個空接口,用於標示對象是否能夠支持序列化,經過ObjectOutputStrean及ObjectInputStream實現序列化和反序列化的過程。注意能夠爲須要序列化的對象設置一個serialVersionUID,在反序列化的時候系統會檢測文件中的serialVersionUID是否與當前類的值一致,若是不一致則說明類發生了修改,反序列化失敗。所以對於可能會修改的類最好指定serialVersionUID的值。
Parcelable是Android特有的一個實現序列化的接口,在Parcel內部包裝了可序列化的數據,能夠在Binder中自由傳輸。序列化的功能由writeToParcel方法來完成,最終經過Parcel的一系列write方法完成。反序列化功能由CREAOR來完成,其內部標明瞭如何建立序列化對象和數組,並經過Parcel的一系列read方法來完成反序列化的過程。
Fragment可見狀態改變時會被調用setUserVisibleHint()方法,能夠經過複寫該方法實現Fragment的懶加載,但須要注意該方法可能在onVIewCreated以前調用,須要確保界面已經初始化完成的狀況下再去加載數據,避免空指針。
Fragment的懶加載
緩存區別:
優勢 RecylerView提供了局部刷新的接口,經過局部刷新,就能避免調用許多無用的bindView。 RecyclerView的擴展性更強大(LayoutManager、ItemDecoration等)。
Android中的Dalvik虛擬機相較於Java虛擬機針對手機的特色作了不少優化。
Dalvik基於寄存器,而JVM基於棧。在基於寄存器的虛擬機裏,能夠更爲有效的減小冗餘指令的分發和減小內存的讀寫訪問。
Dalvik通過優化,容許在有限的內存中同時運行多個虛擬機的實例,而且每個 Dalvik應用做爲一個獨立的Linux進程執行。
java虛擬機運行的是java字節碼。(java類會被編譯成一個或多個字節碼.class文件,打包到.jar文件中,java虛擬機從相應的.class文件和.jar文件中獲取相應的字節碼) Dalvik運行的是自定義的.dex字節碼格式。(java類被編譯成.class文件後,會經過一個dx工具將全部的.class文件轉換成一個.dex文件,而後dalvik虛擬機會從其中讀取指令和數據)
Android開發之淺談java虛擬機和Dalvik虛擬機的區別
查看當前鏈接的設備:adb devices 安裝應用:adb install -r <apk_path> -r表示覆蓋安裝 卸載apk:adb uninstall
APK主要由如下幾部分組成:
其中佔據較大內存的是res資源、lib、class.dex,所以咱們能夠從下面的幾個方面下手:
lint
工具來檢測沒有使用到的資源,或者在gradle中配置shrinkResources
來刪除包括庫中全部的無用的資源,須要配合proguard壓縮代碼使用。這裏須要注意項目中是否存在使用getIdentifier方式獲取資源,這種方式相似反射lint及shrinkResources沒法檢測狀況。若是存在這種方式,則須要配置一個keep.xml來記錄使用反射獲取的資源。壓縮代碼和資源緩存的響應頭:
Cache-control:標明緩存的最大存活時常; Date:服務器告訴客戶端,該資源的發送時間; Expires:表示過時時間(該字段是1.0的東西,當cache-control和該字段同時存在的條件下,cache-control的優先級更高); Last-Modified:服務器告訴客戶端,資源的最後修改時間; 還有一個字段,這個圖沒給出,就是E-Tag:當前資源在服務器的惟一標識,可用於判斷資源的內容是否被修改了。 除以上響應頭字段之外,還需瞭解兩個相關的Request請求頭:If-Modified-since、If-none-Match。這兩個字段是和Last-Modified、E-Tag配合使用的。大體流程以下: 服務器收到請求時,會在200 OK中回送該資源的Last-Modified和ETag頭(服務器支持緩存的狀況下才會有這兩個頭哦),客戶端將該資源保存在cache中,並記錄這兩個屬性。當客戶端須要發送相同的請求時,根據Date + Cache-control來判斷是否緩存過時,若是過時了,會在請求中攜帶If-Modified-Since和If-None-Match兩個頭。兩個頭的值分別是響應中Last-Modified和ETag頭的值。服務器經過這兩個頭判斷本地資源未發生變化,客戶端不須要從新下載,返回304響應。
OkHttpClient經過newCall能夠將一個Request構建成一個Call,Call表示準備被執行的請求。Call調用executed或enqueue會調用Dispatcher對應的方法在當前線程或者一步開始執行請求,通過RealInterceptorChain得到最終結果,RealInterceptorChain是一個攔截器鏈,其中依次包含如下攔截器:
Retrofit採用動態代理,建立聲明service接口的實現對象。當咱們調用service的方法時候會執行InvocationHandler的invoke方法。在這方法中:首先,經過method把它轉換成ServiceMethod,該類是對聲明方法的解析,能夠進一步將設定參數變成Request ;而後,經過serviceMethod, args獲取到okHttpCall 對象,實際調用okhttp的網絡請求方法就在該類中,而且會使用serviceMethod中的responseConverter對ResponseBody轉化;最後,再把okHttpCall進一步封裝成聲明的返回對象(默認是ExecutorCallbackCall,將本來call的回調轉發至UI線程)。
Retrofit2使用詳解及從源碼中解析原理
Retrofit2 徹底解析 探索與okhttp之間的關係
在Activity中,定義一個Observable(Subject),在不一樣的生命週期發射不一樣的事件; 經過compose操做符(內部實際上仍是依賴takeUntil操做符),定義了上游數據,當其接收到Subject的特定事件時,取消訂閱; Subject的特定事件並不是是ActivityEvent,而是簡單的boolean,它已經內部經過combineLast操做符進行了對應的轉化。
程序在啓動的時候,並不會一次性加載程序所要用的全部class文件,而是根據程序的須要,經過Java的類加載機制(ClassLoader)來動態加載某個class文件到內存當中的,從而只有class文件被載入到了內存以後,才能被其它class所引用。因此ClassLoader就是用來動態加載class文件到內存當中用的。
類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個階段。其中準備、驗證、解析3個部分統稱爲鏈接(Linking)。
Java中存在3種類加載器: (1) Bootstrap ClassLoader : 將存放於<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,而且是虛擬機識別的(僅按照文件名識別,如 rt.jar 名字不符合的類庫即便放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。啓動類加載器沒法被Java程序直接引用 。 (2) Extension ClassLoader : 將<JAVA_HOME>\lib\ext目錄下的,或者被java.ext.dirs系統變量所指定的路徑中的全部類庫加載。開發者能夠直接使用擴展類加載器。 (3) Application ClassLoader : 負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者可直接使用。 每一個ClassLoader實例都有一個父類加載器的引用(不是繼承關係,是一個包含的關係),虛擬機內置的類加載器(Bootstrap ClassLoader)自己沒有父類加載器,可是能夠用作其餘ClassLoader實例的父類加載器。 當一個ClassLoader 實例須要加載某個類時,它會試圖在親自搜索這個類以前先把這個任務委託給它的父類加載器,這個過程是由上而下依次檢查的,首先由頂層的類加載器Bootstrap ClassLoader進行加載,若是沒有加載到,則把任務轉交給Extension ClassLoader加載,若是也沒有找到,則轉交給AppClassLoader進行加載,仍是沒有的話,則交給委託的發起者,由它到指定的文件系統或者網絡等URL中進行加載類。尚未找到的話,則會拋出CLassNotFoundException異常。不然將這個類生成一個類的定義,並將它加載到內存中,最後返回這個類在內存中的Class實例對象。
JVM在判斷兩個class是否相同時,不只要判斷兩個類名是否相同,還要判斷是不是同一個類加載器加載的。
在JDK1.6,JDK1.7中,HashMap採用數組+鏈表實現,即便用鏈表處理衝突,同一hash值的鏈表都存儲在一個鏈表裏。可是當位於一個鏈表中的元素較多,即hash值相等的元素較多時,經過key值依次查找的效率較低。而JDK1.8中,HashMap採用位數組+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減小了查找時間。
當鏈表數組的容量超過初始容量*加載因子(默認0.75)時,再散列將鏈表數組擴大2倍,把原鏈表數組的搬移到新的數組中。爲何須要使用加載因子?爲何須要擴容呢?由於若是填充比很大,說明利用的空間不少,若是一直不進行擴容的話,鏈表就會愈來愈長,這樣查找的效率很低,擴容以後,將原來鏈表數組的每個鏈表分紅奇偶兩個子鏈表分別掛在新鏈表數組的散列位置,這樣就減小了每一個鏈表的長度,增長查找效率。
HashMap是非線程安全的,HashTable、ConcurrentHashMap是線程安全的。 HashMap的鍵和值都容許有null存在,而HashTable、ConcurrentHashMap則都不行。 由於線程安全、哈希效率的問題,HashMap效率比HashTable、ConcurrentHashMap的都要高。 HashTable裏使用的是synchronized關鍵字,這實際上是對對象加鎖,鎖住的都是對象總體,當Hashtable的大小增長到必定的時候,性能會急劇降低,由於迭代時須要被鎖定很長的時間。 ConcurrentHashMap引入了分割(Segment),能夠理解爲把一個大的Map拆分紅N個小的HashTable,在put方法中,會根據hash(paramK.hashCode())來決定具體存放進哪一個Segment,若是查看Segment的put操做,咱們會發現內部使用的同步機制是基於lock操做的,這樣就能夠對Map的一部分(Segment)進行上鎖,這樣影響的只是將要放入同一個Segment的元素的put操做,保證同步的時候,鎖住的不是整個Map(HashTable就是這麼作的),相對於HashTable提升了多線程環境下的性能,所以HashTable已經被淘汰了。
Java中HashMap底層實現原理(JDK1.8)源碼分析
Fail-Fast是Java集合的一種錯誤檢測機制。當遍歷集合的同時修改集合或者多個線程對集合進行結構上的改變的操做時,有可能會產生fail-fast機制,記住是有可能,而不是必定。其實就是拋出ConcurrentModificationException 異常。
集合的迭代器在調用next()、remove()方法時都會調用checkForComodification()方法,該方法主要就是檢測modCount == expectedModCount ? 若不等則拋出ConcurrentModificationException 異常,從而產生fail-fast機制。modCount是在每次改變集合數量時會改變的值。
Java程序中wait 和 sleep都會形成某種形式的暫停,它們能夠知足不一樣的須要。wait()方法用於線程間通訊,若是等待條件爲真且其它線程被喚醒時它會釋放鎖,而 sleep()方法僅僅釋放CPU資源或者讓當前線程中止執行一段時間,但不會釋放鎖。
Java代碼在編譯後會變成Java字節碼,字節碼被類加載器加載到JVM裏,JVM執行字節碼,最終須要轉化爲彙編指令在CPU上執行。 volatile是輕量級的synchronized(volatile不會引發線程上下文的切換和調度),它在多處理器開發中保證了共享變量的「可見性」。可見性的意思是當一個線程修改一個共享變量時,另一個線程能讀到這個修改的值。
因爲內存訪問速度遠不及CPU處理速度,爲了提升處理速度,處理器不直接和內存進行通訊,而是先將系統內存的數據讀到內部緩存後在進行操做,但操做完不知道什麼時候會寫到內存。普通共享變量被修改以後,何時被寫入主存是不肯定的,當其餘線程去讀取時,此時內存中可能仍是原來的舊值,所以沒法保證可見性。若是對聲明瞭volatile的變量進行寫操做,JVM就會想處理器發送一條Lock前綴的指令,表示將當前處理器緩存行的數據寫回到系統內存。
不安全。volatile只能保證可見性,並不能保證原子性。i++實際上會被分紅多步完成:1)獲取i的值;2)執行i+1;3)將結果賦值給i。volatile只能保證這3步不被重排序,多線程狀況下,可能兩個線程同時獲取i,執行i+1,而後都賦值結果2,實際上應該進行兩次+1操做。
可使用java.util.concurrent.atomic包下的原子類,如AtomicInteger。
其實現原理是採用CAS自旋操做更新值。CAS即compare and swap的縮寫,中文翻譯成比較並交換。CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。自旋就是不斷嘗試CAS操做直到成功爲止。
Java中每一個對象均可以做爲鎖:
當一個線程試圖訪問同步代碼塊時,它首先必須獲得鎖,退出或拋出異常時必須釋放鎖。synchronized用的鎖是存在Java對象頭裏的MarkWord,一般是32bit或者64bit,其中最後2bit表示鎖標誌位
Java SE1.6爲了減小得到鎖和釋放鎖帶來的性能消耗,引入了偏向鎖和輕量級鎖,在1.6中鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾種狀態會隨着競爭狀況逐漸升級。鎖能夠升級但不能降級。
偏向鎖獲取過程:
好處:1)下降資源消耗;2)提升相應速度;3)提升線程的可管理性。 線程池的實現原理:
這題考的實際上是多線程同步的問題。這種狀況能夠可使用thread.join();join方法會阻塞直到thread線程終止才返回。更復雜一點的狀況也可使用CountDownLatch,CountDownLatch的構造接收一個int參數做爲計數器,每次調用countDown方法計數器減一。作數據處理的線程調用await方法阻塞直到計數器爲0時。
interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除然後者不會。Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識爲true。當中斷線程調用靜態方法Thread.interrupted()來 檢查中斷狀態時,中斷狀態會被清零。而非靜態方法isInterrupted()用來查詢其它線程的中斷狀態且不會改變中斷狀態標識。簡單的說就是任何拋 出InterruptedException異常的方法都會將中斷狀態清零。不管如何,一個線程的中斷狀態有有可能被其它線程調用中斷來改變。
同步的懶加載雖然是線程安全的,可是致使性能開銷。所以產生了雙重檢查鎖定。但雙重檢查鎖定存在隱藏的問題。instance = new Instance()
實際上會分爲三步操做:1)分配對象的內存空間;2)初始化對象;3)設置instance指向剛分配的內存地址;因爲指令重排序,2和3的順序並不肯定。在多線程的狀況下,第一個線程執行了1,3,此時第二個線程判斷instance不爲null,但實際上操做2尚未執行,第二個線程就會得到一個還未初始化的對象,直接使用就會形成空指針。
解決方案是用volatile修飾instance,在JDK 1.5增強了volatile的語意以後,用volatile修飾instance就阻止了2和3的重排序,進而避免上述狀況的發生。
另外一種方式則是使用靜態內部類:
public class Singleton {
private static class InstanceHolder {
public static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return InstanceHolder.instance;
}
}
複製代碼
其原理是利用類初始化時會加上初始化鎖確保類對象的惟一性。
ThreadLocal即線程變量,它爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。從線程的角度看,目標變量就象是線程的本地變量,這也是類名中「Local」所要表達的意思。ThreadLocal的實現是以ThreadLocal對象爲鍵。任意對象爲值得存儲結構。這個結構被附帶在線程上,也就是說一個線程能夠根據一個ThreadLocal對象查詢到綁定在這個線程上的一個值。
數據競爭的定義:在一個線程寫一個變量,在另外一個線程讀同一個變量,並且寫和讀沒有經過同步來排序。
JM屏蔽各類硬件和操做系統的內存訪問差別,以實現讓Java程序在各類平臺下都能達到一致的內存訪問效果。
線程之間的共享變量存儲在主內存中,每一個線程都有一個私有的本地內存,本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是一個抽象概念,它涵蓋了緩存、寫緩存區、寄存器以及其餘的硬件和編譯器優化。 在執行程序時,爲了提升性能,編譯器和處理器經常會對指令作重排序。在多線程中重排序會對程序的執行結果有影響。
JSR-133內存模型採用happens-before的概念來闡述操做之間的內存可見性。happens-before會限制重排序以知足規則。 主要的happens-before規則有以下: