1. 程式人生 > 其它 >ios如何快速轉型安卓開發-專題1

ios如何快速轉型安卓開發-專題1

1.java基礎

1.JavaObject類方法

Object類位於java.lang包中,java.lang包含Java最基礎和核心的類,編譯時自動匯入;
Object類是所有Java類的祖先。所有類將Object作為超累。所有物件都實現此類的方法,可以使用Object的變數指向任意型別的物件。
Object類中的方法:registerNatives()、getClass()、hashCode()、equals()、clone()、toString()、notify()、notifyAll()、wait(long)、wait(long,int)、wait()、finalize()。
registerNatives():註冊方法,向JVM進行註冊。static方法,保證父類的類變數及方法處理化早於子類,確保能夠呼叫到JVM的native方法。
getClass():獲取類物件,當前執行時類的所有資訊的集合。反射的三種方式:getClass,.class,Class.forName。在編寫程式碼時,可以將子物件賦值給父類的一個引用,在執行時通過反射獲取物件的所有資訊。
hashCode():返回當前物件的hash值
equals():比較當前物件與目標物件是否相等,預設比較引用是否指向同一物件
clone():返回物件的一個副本,淺拷貝
toString():類名+hash值
wait():執行緒間通訊,阻塞當前執行緒
notify():喚起執行緒
finalize():垃圾回收機制
物件在記憶體中的狀態:
(1)可達狀態:引用指向
(2)可恢復狀態:失去引用
(3)不可達狀態:垃圾回收

2.hashmap原理,hash衝突,同步集合和併發集合及實現原理

1.hashmap原理

通過hash的方式,通過put和get獲取物件
儲存物件時:將K/V傳給put方法時,通過hashCode計算hash值得到位置,進行儲存,根據當前物件大小自動調整容量。
獲取物件:通過hashCode計算位置,通過equals()確定元素。
衝突處理:發生衝突元素超過閾值,使用紅黑樹代替原有的連結串列,提高速度

2.hash衝突

拉鍊法:根據hash值找到對應的連結串列,相同hash值插入到對應的連結串列之後
線性探測法:找到對應的hash值,衝突後線性查詢
紅黑樹:允許區域性的不平衡,少去過度追求平衡帶來的影響。
同步集合:
1.hashtable是一個散列表,繼承於Dictionary,實現了Map、Cloneable、java.io.Serializable介面。hashtable的函式都是同步的,執行緒安全,key、value非空。
2.Vector是java中可以實現自動增長的物件陣列,vector類實現了一個動態陣列。同步訪問。
併發集合
1.ConcurrentHashMap支援完全併發的檢索和更新,遵守hashtable想用的功能。操作執行緒安全,可以通過程式完全與hashtable互動。
2.ConcurrentSkipListMap 基於跳錶的實現,支援key有序排列的key-value,使用空間換時間,基於樂觀鎖實現高併發。
3.ConcurrentSkipListSet 併發的訪問有序的set。
4.CopyOnWriteArrayList是Array的一個執行緒安全的變形,其中所有的可變操作通過對基礎陣列進行新的複製實現。
5.CopyOnWriteArraySet執行緒安全的無序的集合,執行緒安全的HashSet,通過動態陣列實現
6.ConcurrentLinkedQueue是一個基於連結節點的、無界的、執行緒安全的佇列。
執行緒安全集合以及實現原理
hashmap執行緒是不安全的,如果需要執行緒安全,需要使用collection類的synchronizedMap(),在初始化時候就使用。

3.hashtable與hashmap區別

hashtable是java最開始釋出提供的鍵值對映的資料結構,是執行緒安全的,效率比較低。
hashMap繼承AbstractMap類,hashtable繼承Dictionary類(Dictionary已經被廢棄)。
hashMap需要自己處理執行緒安全,hashtable不需要。
hahstable預設的初始大小11,每次容量擴充為原來的2n+1,hashMap初始為16,每次擴容為2n。
hashtable這樣處理為了減少衝突,容量每次儘量用素數。hashMap為了增加速度,將取模的操作轉化為位運算,加快效率。
由此引出兩個對於hash值的處理也不相同,hashMap根據hashCode計算出值之後,再進行一些位運算,使其更加分散。

4.ArrayList與LinkedList的區別和聯絡

ArrayList使用陣列實現,擴容公式3*/2+1。
LinkedList使用連結串列實現,插入刪除效率同陣列和連結串列區別。

5.GC實現機制

垃圾收集機制,在JVM進行垃圾回收,場所涉及堆和方法去。堆中儲存了Java程式執行時的所有物件資訊,垃圾回收對那些‘dead’物件進行記憶體釋放。將堆記憶體進行分塊處理,分為新生代和老生代,新生代分為三個模組:Eden/From Survivor/To Survivor,每次使用Eden和其中的一塊Survivor,進行垃圾回收的時候,將EdenheSurvivor中存活的物件複製到另一塊Survivor,直至兩個區域內的物件都被回收完成。當Survivor空間不夠的時候,依賴老生代的記憶體進行分配,無法存方式,這些存活物件將直接通過分配擔保機制進入老生代。老生代不止儲存這些物件,還儲存著一些大物件。Java程式執行的時候如果遇到大物件或者長期存活的物件,就會將其放入老生代。方法區被人們稱為永久代,存放在永久代的物件一般不會被回收,一般主要回收廢棄變數和無用類。
類無用的三個條件:
(1)該類的所有例項被回收
(2)載入該類的classLoader已經被回收
(3)該類對應的java.lang.Class物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
虛擬機器可以對滿足上述條件的無用類進行回收。
物件每熬過一次Minor GC後,年齡會增加一歲,當年齡增加到15時便會晉升到老年代。特殊條件:在Survivor空間所有相同年齡的物件大小的總和大雨Survivor空間的一半,大於或者等於該年齡的物件可以進入老年代。
回收的方法:
可達性分析演算法:從GC Roots開始搜尋,當一個物件到GCRoots沒有任何引用鏈相連的時候,證明該物件是不可用的。
JVM回收機制:第一次判斷沒有引用鏈的時候,新增第一次逼哦阿吉,並篩選該物件有沒有必要執行finalize()方法,沒有必要的時候放置在F-Queue的佇列中,在虛擬機器自動建立的低優先順序的執行緒中去執行。如果在finalize方法中該物件與任何一個引用鏈上的物件有聯絡,該物件就會脫離垃圾回收系統。如果該物件在finalize方法中沒有與物件關聯,物件會被二次標記,被系統回收。
垃圾回收演算法:
(1)標記清除演算法
(2)複製演算法:將可用記憶體劃分成大小相等的兩塊,每次使用其中的一塊。當一塊的記憶體使用完了,就將還存活的物件複製到另外一塊上,把已經使用的記憶體空間一次清除掉。

6.java代理模式和反射機制

代理模式的總用:為其他物件提供一種代理以控制對這個物件的訪問。在某些情況下,一個客戶不想活著不能直接飲用另一個物件,代理物件可以在客戶端和目標物件中起到一箇中介的作用。
代理模式設計的角色:
抽象角色:申明真實物件和代理物件之間的共同介面;
代理角色:代理物件內部含有對真實物件的引用,從而可以操作真實物件,同時代理物件提供與真實物件相同的介面以便在任何時候能夠代替真實物件。同時,代理物件可以在執行真實物件操作,附加其他的操作,可以對真實物件進行封裝。
真實角色:代理角色所代表的真實物件,是我們最終要引用的物件
java動態代理類
java動態代理位於java.lang.reflect包下,涉及兩個類:(1)Interfave InvocationHandler:該介面僅僅定義了一個方法Object:Invoke(Object obj, Method method,Object[] args)。obj代表代理類,method是被代理的方法,args是該方法的引數陣列。這個抽象方法在代理類中動態實現。(2)Proxy:該類指動態代理類,主要包含以下內容:Protected Proxy(InvocationHandler h)建構函式, static class getProxyClass(ClassLoader loader,Class[] interfaces):獲得一個代理類,其中loader是類裝載器,interfaces是真實類所擁有的全部介面的陣列,static object newProxyInstance(CLassLoader loader,Class[] interfaces,InvocationHandler h):返回代理類的一個例項,返回後的代理類可以被當作當代理類使用
Dynamic Proxy:執行時生成的class,生成時需要提供一組interface,class宣稱實現了這些interface,可以把class的例項當作這些interface的任何一個使用。這個Dynamic Proxy是一個Proxy,不會做實質性的工作,在生成他的例項時必須提供一個handler,處理實際的工作。

點選檢視程式碼
         RealSubject realSubject=new RealSubject();
         Class<?>cla=realSubject.getClass();
         InvocationHandler handler=new ProxySubject(realSubject);
         Subject subject=(Subject)Proxy.newProxyInstance(cla.getClassLoader(), cla.getInterfaces(), handler);
         subject.request();

7.java泛型

泛型,引數化型別。將型別由原來的集體的型別引數化,類似於方法中的變數引數,此時型別也定義成引數型別,然後在呼叫/使用時傳入具體的型別。簡單用法 List傳入對應的型別實參,提前暴漏對應的問題,不採用對應的方法處理的時候,在取到對應的資料的時候才能判斷對應的型別,提前限制List的屬性,可以在add的時候就確認對應的問題。
傳入不同泛型類在記憶體上只有一個,還是原來最基本的型別。泛型只會存在程式碼編譯階段,檢測出對應的結果後,對應的泛型的資訊會被擦除,不會進入到執行時階段。邏輯上是多個不同的型別,但實際上都是相同的基本型別。
坑點:java型別萬用字元

8.synchronized原理

1.synchronized的三種應用方式:方法鎖,物件鎖,類鎖。
方法鎖:
(1)修飾例項方法,作用於當前例項加鎖,進入同步程式碼前要獲得當前例項的鎖。
(2)靜態方法,作用於當前類物件加鎖,進入同步程式碼錢要獲得當前類物件的所。
(3)修飾程式碼塊,指定加鎖物件,對給定物件加鎖,進入同步程式碼庫前獲得給定物件的鎖。
synchronized括號後面的物件是一把鎖,所有物件都可以成為鎖,將object比喻成一個key,擁有這個key的執行緒才能執行這個方法,拿到key並執行方法的過程中,這個key是隨身攜帶的,並且只有一把。如果後續的執行緒想要訪問當前方法,因為沒有key不能訪問,只能等待,所以synchronized的物件必須是同一個,如果是不同的物件,意味著是不用房間的鑰匙,對於訪問者是沒有影響的。
synchronized如何實現鎖,為什麼每一個物件都可以成為鎖,鎖存在那個地方
Java物件頭:在hotspot虛擬機器中,物件在記憶體中的佈局分為三塊區域:物件頭、例項物件和對齊補充;Java物件頭是實現synchronized的鎖物件的基礎,使用的鎖物件一般儲存在Java的物件頭裡,是輕量級鎖和偏向鎖的關鍵。
Mark Word:用於儲存物件自身的執行時資料,如雜湊碼、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等。Java物件頭一般佔用兩個機器碼。

點選檢視程式碼
class oopDesc {
  friend class VMStructs;
 private:
  volatile markOop  _mark;//理解為物件頭
  union _metadata {
    Klass*      _klass;
    narrowKlass _compressed_klass; //預設開啟壓縮
  } _metadata;
......

_mark被宣告在oopDesc類的頂部,所以_mark可以認為是一個頭部
monitor:同步工具
鎖存在於每個物件的markOop物件頭重,每個javaObject物件在JVM內部都有一個native的C++物件oop/oopDesc與之對應,對應的oop/oopDesc儲存一個物件頭,這個物件頭是儲存鎖的區域,裡面還有物件監視器。
synchronized如何實現鎖:鎖經過優化,引入了偏向鎖、輕量級鎖;鎖的級別從低到高逐步升級,無鎖-偏向鎖-輕量級鎖-重量級鎖。鎖從巨集觀上分為悲觀鎖和樂觀鎖。
樂觀鎖:
樂觀鎖是一種樂觀思想,認為讀多寫少,遇到併發寫的可能性低,每次拿資料的時候都認為別人不會修改,每次更新的時候判斷一下期間有沒有人更新這個資料,採取在寫時先讀出當前版本號,然後加鎖操作(比較和上一次的版本號,相同則更新),如果失敗,重複讀-寫的操作。java的樂觀鎖通過CAS操作實現,CAS是一種更新的原子操作,比較當前值是否跟傳入值一樣,一樣則更新,否則失敗。
悲觀鎖:
  悲觀鎖是就是悲觀思想,即認為寫多,遇到併發寫的可能性高,每次去拿資料的時候都認為別人會修改,所以每次在讀寫資料的時候都會上鎖,這樣別人想讀寫這個資料就會block直到拿到鎖。java中的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嚐試cas樂觀鎖去獲取鎖,獲取不到,才會轉換為悲觀鎖,如RetreenLock。

自旋鎖(CAS):
  自旋鎖就是讓不滿足條件的執行緒等待一段時間,而不是立即掛起。看持有鎖的執行緒是否能夠很快釋放鎖。怎麼自旋呢?其實就是一段沒有任何意義的迴圈。雖然它通過佔用處理器的時間來避免執行緒切換帶來的開銷,但是如果持有鎖的執行緒不能在很快釋放鎖,那麼自旋的執行緒就會浪費處理器的資源,因為它不會做任何有意義的工作。所以,自旋等待的時間或者次數是有一個限度的,如果自旋超過了定義的時間仍然沒有獲取到鎖,則該執行緒應該被掛起。JDK1.6中-XX:+UseSpinning開啟; -XX:PreBlockSpin=10 為自旋次數; JDK1.7後,去掉此引數,由jvm控制;

偏向鎖:
  大多數情況下,鎖不僅不存在多執行緒競爭,而且總是由同一執行緒多次獲得,為了讓執行緒獲得鎖的代價更低而引入了偏向鎖。下圖就是偏向鎖的獲得跟撤銷流程圖:

當一個執行緒訪問同步塊並獲取鎖時,會在物件頭和棧幀中的鎖記錄裡儲存鎖偏向的執行緒ID,以後該執行緒在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下物件頭的Mark Word裡是否儲存著指向當前執行緒的偏向鎖。如果測試成功,表示執行緒已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設定成01(表示當前是偏向鎖):如果沒有設定,則使用CAS競爭鎖;如果設定了,則嘗試使用CAS將物件頭的偏向鎖指向當前執行緒。執行同步塊。這個時候執行緒2也來訪問同步塊,也是會檢查物件頭的Mark Word裡是否儲存著當前執行緒2的偏向鎖,發現不是,那麼他會進入 CAS 替換,但是此時會替換失敗,因為此時執行緒1已經替換了。替換失敗則會進入撤銷偏向鎖,首先會去暫停擁有了偏向鎖的執行緒1,進入無鎖狀態(01).偏向鎖存在競爭的情況下就回去升級成輕量級鎖。
開啟:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 -client -Xmx1024m -Xms1024m
關閉:-XX:+UseBiasedLocking -client -Xmx512m -Xms512m

輕量級鎖(todo):
  引入輕量級鎖的主要目的是在多沒有多執行緒競爭的前提下,減少傳統的重量級鎖使用作業系統互斥量產生的效能消耗。當關閉偏向鎖功能或者多個執行緒競爭偏向鎖導致偏向鎖升級為輕量級鎖,則會嘗試獲取輕量級鎖。
在程式碼進入同步塊的時候,如果同步物件鎖狀態為無鎖狀態(鎖標誌位為“01”狀態),虛擬機器首先將在當前執行緒的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於儲存鎖物件目前的Mark Word的拷貝,官方稱之為 Displaced Mark Word。這個時候 JVM會嘗試使用 CAS 將 mark Word 更新為指向棧幀中的鎖記錄(Lock Record)的空間指標。並且把鎖標誌位設定為 00(輕量級鎖標誌),與此同時如果有另外一個執行緒2也來進行 CAS 修改 Mark Word,那麼將會失敗,因為執行緒1已經獲取到該鎖,然後執行緒2將會進行 CAS操作不斷的去嘗試獲取鎖,這個時候將會引起鎖膨脹,就會升級為重量級鎖,設定標誌位為 10.
  由輕量鎖切換到重量鎖,是發生在輕量鎖釋放鎖的期間,之前在獲取鎖的時候它拷貝了鎖物件頭的markword,在釋放鎖的時候如果它發現在它持有鎖的期間有其他執行緒來嘗試獲取鎖了,並且該執行緒對markword做了修改,兩者比對發現不一致,則切換到重量鎖。輕量級解鎖時,會使用原子的CAS操作來將Displaced Mark Word替換回到物件頭,如果成功,則表示同步過程已完成。如果失敗,表示有其他執行緒嘗試過獲取該鎖,則要在釋放鎖的同時喚醒被掛起的執行緒進入等待。

重量級鎖(todo):
  重量級鎖通過物件內部的監視器(monitor)實現,其中monitor的本質是依賴於底層作業系統的Mutex Lock實現,作業系統實現執行緒之間的切換需要從使用者態到核心態的切換,切換成本非常高。主要是,當系統檢查到鎖是重量級鎖之後,會把等待想要獲得鎖的執行緒進行阻塞,被阻塞的執行緒不會消耗cup。但是阻塞或者喚醒一個執行緒時,都需要作業系統來幫忙,這就需要從使用者態轉換到核心態,而轉換狀態是需要消耗很多時間的,有可能比使用者執行程式碼的時間還要長。這就是說為什麼重量級執行緒開銷很大的。
  monitor這個物件,在hotspot虛擬機器中,通過ObjectMonitor類來實現 monitor。他的鎖的獲取過程的體現會簡單很多。每個object的物件裡 markOop->monitor() 裡可以儲存ObjectMonitor的物件。

wait和notify的原理:
  呼叫wait方法,首先會獲取監視器鎖,獲得成功以後,會讓當前執行緒進入等待狀態進入等待佇列並且釋放鎖;然後當其他執行緒呼叫notify或者notifyall以後,會通知等待執行緒可以醒了,而執行完notify方法以後,並不會立馬喚醒執行緒,原因是當前的執行緒仍然持有這把鎖,處於等待狀態的執行緒無法獲得鎖。必須要等到當前的執行緒執行完按monitorexit指令以後,也就是鎖被釋放以後,處於等待佇列中的執行緒就可以開始競爭鎖了。
wait和notify為什麼需要在synchronized裡面:
  wait方法的語義有兩個,一個是釋放當前的物件鎖、另一個是使得當前執行緒進入阻塞佇列, 而這些操作都和監視器是相關的,所以wait必須要獲得一個監視器鎖。
  而對於notify來說也是一樣,它是喚醒一個執行緒,既然要去喚醒,首先得知道它在哪裡?所以就必須要找到這個物件獲取到這個物件的鎖,然後到這個物件的等待佇列中去喚醒一個執行緒。

9.volatitle原理

1.volatile的記憶體語義
1.1 volatile的特性
一個volatile變數自身具有以下三個特性:
可見性:即當一個執行緒修改了宣告為volatile變數的值,新值對於其他要讀該變數的執行緒來說是立即可見的。而普通變數是不能做到這一點的,普通變數的值線上程間傳遞需要通過主記憶體來完成。
有序性:volatile變數的所謂有序性也就是被宣告為volatile的變數的臨界區程式碼的執行是有順序的,即禁止指令重排序。

受限原子性:這裡volatile變數的原子性與synchronized的原子性是不同的,synchronized的原子性是指只要宣告為synchronized的方法或程式碼塊兒在執行上就是原子操作的。而volatile是不修飾方法或程式碼塊兒的,它用來修飾變數,對於單個volatile變數的讀/寫操作都具有原子性,但類似於volatile++這種複合操作不具有原子性。所以volatile的原子性是受限制的。並且在多執行緒環境中,volatile並不能保證原子性。

1.2 volatile寫-讀的記憶體語義
volatile寫的記憶體語義:當寫執行緒寫一個volatile變數時,JMM會把該執行緒對應的本地記憶體中的共享變數值重新整理到主記憶體。

volatile讀的記憶體語義:當讀執行緒讀一個volatile變數時,JMM會把該執行緒對應的本地記憶體置為無效,執行緒接下來將從主記憶體讀取共享變數。

2.volatile語義實現原理
在介紹volatile語義實現原理之前,我們先來看兩個與CPU相關的專業術語:

記憶體屏障(memory barriers):一組處理器指令,用於實現對記憶體操作的順序限制。
快取行(cache line):CPU快取記憶體中可以分配的最小儲存單位。處理器填寫快取行時會載入整個快取行。
2.1 volatile可見性實現原理

。java原始碼編譯後的位元組碼檔案是不能夠直接被CPU執行的,那麼該如何執行呢?答案是JVM,為了讓java程式能夠在不同的平臺上執行,java官方提供了針對於各個平臺的java虛擬機器,JVM運行於硬體層之上,遮蔽各種平臺的差異性。

為了提高處理速度,處理器一般不直接和記憶體通訊,而是先將系統記憶體的資料讀到內部快取後再進行操作,但操作完成後並不知道處理器何時將快取資料寫回到記憶體。但如果對加了volatile修飾的變數進行寫操作,JVM就會向處理器傳送一條lock字首的指令,將這個變數在快取行的資料寫回到系統記憶體。這時只是寫回到系統記憶體,但其他處理器的快取行中的資料還是舊的,要使其他處理器快取行的資料也是新寫回的系統記憶體的資料,就需要實現快取一致性協議。即在一個處理器將自己快取行的資料寫回到系統記憶體後,其他的每個處理器就會通過嗅探在總線上傳播的資料來檢查自己快取的資料是否已過期,當處理器發現自己快取行對應的記憶體地址的資料被修改後,就會將自己快取行快取的資料設定為無效,當處理器要對這個資料進行修改操作的時候,會重新從系統記憶體中把資料讀取到自己的快取行,重新快取。
volatile可見性的實現就是藉助了CPU的lock指令,通過在寫volatile的機器指令前加上lock字首,使寫volatile具有以下兩個原則:
寫volatile時處理器會將快取寫回到主記憶體。
一個處理器的快取寫回到記憶體會導致其他處理器的快取失效。
https://blog.csdn.net/yelang0/article/details/100364594 (todo)

10.方法鎖、物件鎖、類鎖的意義和區別

方法鎖:正常的函式加上synchronized,控制對類成員物件的訪問
物件鎖:synchronized(obj),併發物件修改操作
類鎖:靜態方法,synchronized(obj.class),obj的抽象形式

11.執行緒同步的方法:Synchronized、lock、reentrantLock分析

Synchronized:被某個執行緒獲取到之後,其他執行緒會被阻塞
Lock:介面,提供比Synchronized更加廣泛的鎖操作
void lock():
獲得鎖
void lockInterruptibly():
獲取鎖,如果可用則立即返回,如果不可用,則處於休眠。
Condition newCondition();
返回Condition繫結該例項的Lock例項。
Boolean tryLock():
如果可用,則獲取鎖,並返回true。如果鎖不可用,則返回false。
Boolean tryLock(long time, TimeUnit unit):
如果獲取到鎖,則立即返回true,否則會等待給定的引數時間,在等待的過程中,獲取到返回true,等待超時返回false。
void unlock():
釋放鎖
當使用lockInterruptibly()方法的時候,如果沒有獲取到鎖,則執行緒進入休眠,但是在休眠的過程中,當前執行緒可以被其他執行緒中斷(會受到InterruptedException異常),也就是鎖有兩個執行緒 A,B,獲取鎖,B獲取到鎖了,然後A進行休眠,但是這個時候可以呼叫A.interrupt()方法中斷A的休眠。
當使用synchronized修飾的時候,當一個執行緒處於等待的時候是不能被中斷的。
ReentrantLock:可重入鎖
Lock是java語言寫的,是一個介面。而Synchronized是java內建的。
lock獲取的鎖是可以中斷的,而Synchronized則是一直阻塞,直到獲取鎖。
Synchronized執行完後會自動釋放鎖,而lock必須手動的釋放鎖,不然容易造成死鎖。
通過lock可以知道當前執行緒有沒有獲取到鎖。

讀寫鎖 將一個資源分成了兩個鎖,分別是讀鎖,和些鎖。
可以通過readLock()獲取讀鎖,writeLock()獲的寫鎖。
使用讀鎖可以被大量的執行緒訪問,但是要保證是同一物件。使用寫鎖在同一時間內只能被一個執行緒訪問。

11.ThreadLocal的原理和用法

1、ThreadLocal.get: 獲取ThreadLocal中當前執行緒共享變數的值。
2、ThreadLocal.set: 設定ThreadLocal中當前執行緒共享變數的值。
3、ThreadLocal.remove: 移除ThreadLocal中當前執行緒共享變數的值。
4、ThreadLocal.initialValue: ThreadLocal沒有被當前執行緒賦值時或當前執行緒剛呼叫remove方法後呼叫get方法,返回此方法值。
執行緒共享變數快取如下:
Thread.ThreadLocalMap<ThreadLocal, Object>;
1、Thread: 當前執行緒,可以通過Thread.currentThread()獲取。
2、ThreadLocal:我們的static ThreadLocal變數。
3、Object: 當前執行緒共享變數。
我們呼叫ThreadLocal.get方法時,實際上是從當前執行緒中獲取ThreadLocalMap<ThreadLocal, Object>,然後根據當前ThreadLocal獲取當前執行緒共享變數Object。
ThreadLocal.set,ThreadLocal.remove實際上是同樣的道理。
這種儲存結構的好處:
1、執行緒死去的時候,執行緒共享變數ThreadLocalMap則銷燬。
2、ThreadLocalMap<ThreadLocal,Object>鍵值對數量為ThreadLocal的數量,一般來說ThreadLocal數量很少,相比在ThreadLocal中用Map<Thread, Object>鍵值對儲存執行緒共享變數(Thread數量一般來說比ThreadLocal數量多),效能提高很多。
關於ThreadLocalMap<ThreadLocal, Object>弱引用問題:
當執行緒沒有結束,但是ThreadLocal已經被回收,則可能導致執行緒中存在ThreadLocalMap<null, Object>的鍵值對,造成記憶體洩露。(ThreadLocal被回收,ThreadLocal關聯的執行緒共享變數還存在)。
雖然ThreadLocal的get,set方法可以清除ThreadLocalMap中key為null的value,但是get,set方法在記憶體洩露後並不會必然呼叫,所以為了防止此類情況的出現,我們有兩種手段。
1、使用完執行緒共享變數後,顯示呼叫ThreadLocalMap.remove方法清除執行緒共享變數;
2、JDK建議ThreadLocal定義為private static,這樣ThreadLocal的弱引用問題則不存在了。

Android基礎

1.安卓專案結構

1.安卓四大元件
Android系統四大元件分別是Activity、Service、BroadcastReceiver和ContentProvider。其中Activity是所有Android應用程式的門面,凡是在應用中你看得到的東西,都是放在Activity中的。而Service就比較低調了,你無法看到它,但它會在後臺默默地執行,即使使用者退出了應用,Service仍然是可以繼續執行的。BroadcastReceiver允許你的應用接收來自各處的廣播訊息,比如電話、簡訊等,當然,你的應用也可以向外發出廣播訊息。ContentProvider則為應用程式之間共享資料提供了可能,比如你想要讀取系統通訊錄中的聯絡人,就需要通過ContentProvider來實現。
2.專案結構
.gradle和.idea這兩個目錄下放置的都是Android Studio自動生成的一些檔案。
app專案中的程式碼、資源等內容都是放置在這個目錄下的。
build這個目錄主要包含了一些在編譯時自動生成的檔案。
.gitignore這個檔案是用來將指定的目錄或檔案排除在版本控制之外的。
build.gradle這是專案全域性的gradle構建指令碼。
gradle.properties這個檔案是全域性的gradle配置檔案。
gradlew和gradlew.bat這兩個檔案是用來在命令列介面中執行gradle命令的,其中gradlew是在Linux或Mac系統中使用的,gradlew.bat是在Windows系統中使用的。
local.properties這個檔案用於指定本機中的Android SDK路徑,通常內容是自動生成的,我們並不需要修改。除非你本機中的Android SDK位置發生了變化,那麼就將這個檔案中的路徑改成新的位置即可。
settings.gradle這個檔案用於指定專案中所有引入的模組。
(1)app目錄下的結構
build這個目錄和外層的build目錄類似,也包含了一些在編譯時自動生成的檔案,不過它裡面的內容會更加複雜,我們不需要過多關心。
libs如果你的專案中使用到了第三方jar包,就需要把這些jar包都放在libs目錄下,放在這個目錄下的jar包會被自動新增到專案的構建路徑裡。
androidTest此處是用來編寫Android Test測試用例的,可以對專案進行一些自動化測試。
java毫無疑問,java目錄是放置我們所有Java程式碼的地方(Kotlin程式碼也放在這裡),展開該目錄,你將看到系統幫我們自動生成了一個MainActivity檔案。
res這個目錄下的內容就有點多了。簡單點說,就是你在專案中使用到的所有圖片、佈局、字串等資源都要存放在這個目錄下。當然這個目錄下還有很多子目錄,圖片放在drawable目錄下,佈局放在layout目錄下,字串放在values目錄下,所以你不用擔心會把整個res目錄弄得亂糟糟的。
AndroidManifest.xml這是整個Android專案的配置檔案,你在程式中定義的所有四大元件都需要在這個檔案裡註冊,另外還可以在這個檔案中給應用程式新增許可權宣告。由於這個檔案以後會經常用到,我們等用到的時候再做詳細說明。
test此處是用來編寫Unit Test測試用例的,是對專案進行自動化測試的另一種方式。
.gitignore這個檔案用於將app模組內指定的目錄或檔案排除在版本控制之外,作用和外層的.gitignore檔案類似。app.imlIntelliJ IDEA專案自動生成的檔案,我們不需要關心或修改這個檔案中的內容。
build.gradle這是app模組的gradle構建指令碼,這個檔案中會指定很多專案構建相關的配置。
proguard-rules.pro這個檔案用於指定專案程式碼的混淆規則,當代碼開發完成後打包成安裝包檔案,如果不希望程式碼被別人破解,通常會將程式碼進行混淆,從而讓破解者難以閱讀。
(2)res目錄下的結構
res目錄中的內容就變得非常簡單了。所有以“drawable”開頭的目錄都是用來放圖片的,所有以“mipmap”開頭的目錄都是用來放應用圖示的,所有以“values”開頭的目錄都是用來放字串、樣式、顏色等配置的,所有以“layout”開頭的目錄都是用來放佈局檔案的。
引用資源方式
● 在程式碼中通過R.string.app_name可以獲得該字串的引用。
● 在XML中通過@string/app_name可以獲得該字串的引用。
如果是引用的圖片資源就可以替換成drawable,如果是引用的應用圖示就可以替換成mipmap,如果是引用的佈局檔案就可以替換成layout,以此類推。
(3)兩個build.gradle區別
外層build.gradle的檔案,主要設定了庫的依賴,兩處repositories的閉包中都聲明瞭google()和jcenter()這兩行配置,oogle倉庫中包含的主要是Google自家的擴充套件依賴庫,而jcenter倉庫中包含的大多是一些第三方的開源庫。聲明瞭這兩行配置之後,我們就可以在專案中輕鬆引用任何google和jcenter倉庫中的依賴庫了。dependencies閉包中使用classpath聲明瞭兩個外掛。
app目錄下的builde.gradle
第一部分:引用外掛
com.android.application表示這是一個應用程式模組,com.android.library表示這是一個庫模組。
第二部分:一個大的android閉包
在這個閉包中我們可以配置專案構建的各種屬性。其中,compileSdkVersion用於指定專案的編譯版本,這裡指定成29表示使用Android 10.0系統的SDK編譯。buildToolsVersion用於指定專案構建工具的版本。defaultConfig閉包中可以對專案的更多細節進行配置。其中,applicationId是每一個應用的唯一識別符號,絕對不能重複,預設會使用我們在建立專案時指定的包名,如果你想在後面對其進行修改,那麼就是在這裡修改的。minSdkVersion用於指定專案最低相容的Android系統版本,這裡指定成21表示最低相容到Android 5.0系統。targetSdkVersion指定的值表示你在該目標版本上已經做過了充分的測試,系統將會為你的應用程式啟用一些最新的功能和特性。versionCode用於指定專案的版本號,versionName用於指定專案的版本名。最後,testInstrumentationRunner用於在當前專案中啟用JUnit測試,你可以為當前專案編寫測試用例,以保證功能的正確性和穩定性。
buildTypes閉包中用於指定生成安裝檔案的相關配置,通常只會有兩個子閉包:一個是debug,一個是release。debug閉包用於指定生成測試版安裝檔案的配置,release閉包用於指定生成正式版安裝檔案的配置。另外,debug閉包是可以忽略不寫的,因此我們看到上面的程式碼中就只有一個release閉包。release閉包中的具體內容:minifyEnabled用於指定是否對專案的程式碼進行混淆,true表示混淆,false表示不混淆。proguardFiles用於指定混淆時使用的規則檔案。
還剩一個dependencies閉包。這個閉包的功能非常強大,它可以指定當前專案所有的依賴關係。通常Android Studio專案一共有3種依賴方式:本地依賴、庫依賴和遠端依賴。本地依賴可以對本地的jar包或目錄新增依賴關係,庫依賴可以對專案中的庫模組新增依賴關係,遠端依賴則可以對jcenter倉庫上的開源專案新增依賴關係。

2.安卓工程用法

xml用於佈局管理,通過R.layout.newitem進行相關佈局的處理,通過activity中進行相關內容的處理,在Mainfest.xml處理activity的註冊,保證對應的activity是否是啟動開始。
使用intent連結各個activity的活動
(1)顯式Intent
Intent intent = new Intent(FromActivity.this, ToActivity.class);
startActivity(intent);
(2)隱式intent
在對應的AndroidMainfest.xml中設定對應的intentfilter名字,catergory的內容可以匹配Intent指定的action和catergory,這個活動才能響應。
Intent intent = new Intent("activity id");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com")); //開啟網址

1.傳資料給下一個activity

通過intent開始傳值:
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra("extra_data", "Hello Second World");
intent接受值:
Intent intent = getIntent();
Log.d("SecondActivity",intent.getStringExtra("extra_data"));

2.回傳資料給上一個活動

startActivityForResult(intent,1);
Intent intent = new Intent();
intent.putExtra("data_return", "Hello MainActivity");
setResult(RESULT_OK, intent);
finish();
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
switch (requestCode) {
case 1:
if (resultCode ==RESULT_OK) {
String returnedData = data.getStringExtra("data_return");
Log.d("FirstActivity", returnedData);
}break;
default:
}
}