編輯:關於Android編程
先上一個使用ThreadLocal實例的demo,ThreadLocalDemo 實例包含一個ThreadLocal實例。從網上各種信息看到ThreadLocal是線程私有變量。保持了每個變量的副本,其實ThreadLocal不能用於解決多線程共享變量問題。
ThreadLocal 中只是保存該線程自己創建的局部變量的副本。如果是多線程共享的變量還是會發生不能同步該的後果。下面這個例子就是啟動兩個線程,通過threadLocal實例的set方法將person實例加入到線程本地變量ThreadLocal.Values localValues;中。但是localValues這個變量底層實現十基於數組的一個map結構。對於引用變量緩存引用。所以在這個demo中兩個線程的localValues變量都指向了同一個person實例。也就不是線程私有的變量。要達到線程私有的話只有在線程中通過創建的變量通過ThreadLocal的set方法插入的元素才是線程私有的變量。
public class ThreadLocalDemo {
private static Person person;
private static ThreadLocal threadLocal = new ThreadLocal();
public ThreadLocalDemo( Person person ) {
this.person = person;
}
public static void main( String[] args ) throws InterruptedException {
// 多個線程使用同一個Person對象
Person per = new Person(111, "Sone");
ThreadLocalDemo test = new ThreadLocalDemo(per);
Thread th1 = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
threadLocal.set(person);
String threadName = Thread.currentThread().getName();
Person perLocal = threadLocal.get();
System.out.println(threadName + " before:" + threadLocal.get());
perLocal.setId(888);
perLocal.setName("Admin");
System.out.println(threadName + " after:" + threadLocal.get());
}
}, "thread-th1");
Thread th2 = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
threadLocal.set(person);
System.out.println(Thread.currentThread().getName() + " :"
+ threadLocal.get());
}
}, "thread-th2");
th1.start();
th1.join();
Thread.sleep(100);
th2.start();
th2.join();
// Person對象已經被線程給修改了!
System.out.println("Person對象的值為:" + per);
}
}
class Person {
private int id;
private String name;
public Person( int id , String name ) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId( int id ) {
this.id = id;
}
public String getName() {
return name;
}
public void setName( String name ) {
this.name = name;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + "]";
}
}
輸出:
可以看到雖然將person實例加入到兩個線程本地變量ThreadLocal.Values localValues; 但是都是指向同一個實例person。所以從輸出結果可以看到在th1線程更改了person後,在th2線程也可以獲取到最新的結果。
thread-th1 before:Person [id=111, name=Sone]
thread-th1 after:Person [id=888, name=Admin]
thread-th2 :Person [id=888, name=Admin]
Person對象的值為:Person [id=888, name=Admin]
下面給出在線程裡面new一個實例然後通過ThreadLocal類的set方法插入到當前線程的
ThreadLocal.Values localValues;變量中 ,最後對當前線程的localValues變量中的本地變量通過ThreadLocal類的get()方法獲取到當前線程threadLocal實例為key對於的值。
public class ThreadLocalDemo {
private static Person person;
private static ThreadLocal threadLocal = new ThreadLocal();
//定義一個ThreadLocal類實例,這個是插入線程本地變量的接口類,
//有get/set方法;ThreadLocal類其實只是封裝了插入線程本地變量的操作接口,
//每個線程的線程本地變量ThreadLocal.Values localValues就是一個map存儲
//結構,以ThreadLocal類實例為key,存儲的數據為值。
//如果需要獲取到這個本地變量,只需要在線程內部通過ThreadLocal類實例的get()方法就可以獲取到與ThreadLocal類實例對於的值。
public ThreadLocalDemo( Person person ) {
this.person = person;
}
public static void main( String[] args ) throws InterruptedException {
// 多個線程使用同一個Person對象
Person per = new Person(111, "Sone");
ThreadLocalDemo test = new ThreadLocalDemo(per);
Thread th1 = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
threadLocal.set(new Person(111, "Sone"));
String threadName = Thread.currentThread().getName();
Person perLocal = threadLocal.get();
System.out.println(threadName + " before:" + threadLocal.get());
perLocal.setId(9999);
perLocal.setName("jim");
System.out.println(threadName + " after:" + threadLocal.get());
}
}, "thread-th1");
Thread th2 = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
threadLocal.set(new Person(112, "vincent"));
String threadName = Thread.currentThread().getName();
Person perLocal = threadLocal.get();
System.out.println(threadName + " before:" + threadLocal.get());
perLocal.setId(8);
perLocal.setName("jack");
System.out.println(threadName + " after:" + threadLocal.get());
}
}, "thread-th2");
th1.start();
th2.start();
th1.join();
th2.join();
// Person對象已經被線程給修改了!
System.out.println("Person對象的值為:" + per);
}
public void run() {
}
}
class Person {
private int id;
private String name;
public Person( int id , String name ) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId( int id ) {
this.id = id;
}
public String getName() {
return name;
}
public void setName( String name ) {
this.name = name;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + "]";
}
}
輸出結果:
從輸出結果可以看到我在各自線程創建一個person實例,然後插入到線程本地變量中(ThreadLocal.Values localValues; 是一個map結構的數據結構),每個線程有具有一個這樣的實例;插入使用的是相同的ThreadLocal類實例為key,但是緩存的是不同person變量。
thread-th2 before:Person [id=112, name=vincent]
thread-th2 after:Person [id=8, name=jack]
thread-th1 before:Person [id=111, name=Sone]
thread-th1 after:Person [id=9999, name=jim]
Person對象的值為:Person [id=111, name=Sone]
最後說下看ThreadLocal類後對他的理解,首先ThreadLocal不是一個具體的線程。它是一個線程用於存取本地變量 ThreadLocal.Values localValues;的操作類,localValues是一個map類型的數據,key就是ThreadLocal,value就是插入的數據,在一個線程中可以插入不同ThreadLocal實例的數據,一個線程本地變量只能緩存特定ThreadLocal實例的一條數據。
在java中ThreadLocal以Map的形式存儲數據(ThreadLocal對象為 key 數值為value)。在Android中做了些改動,在Thread-Local的add方法中,可以看到它會把ThradLocal對象(key)和相對應的value放在table數組連續的位置中。 也就是table被設計為下標為0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。
先看下android的ThreadLocal類的源碼,其中也就兩個接口方法重要,get()和set(T data);
ThreadLocal數據插入流程
//set(T data)讓線程插入一個key為當前ThreadLocal實例,value為value的鍵值對
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
//values(Thread current)這裡就可以看到其實ThreadLocal類操縱的還是當前線程的本地變量。
Values values(Thread current) {
return current.localValues;
}
//然後對values判定是否為空,如果為空那麼初始化一個空的Values實例,
//如下圖就是初始化了一個空的Values類實例復制給了當前線程的
//ThreadLocal.Values localValues屬性字段;
Values initializeValues(Thread current) {
return current.localValues = new Values();
}
Values() {
initializeTable(INITIAL_SIZE);
this.size = 0;
this.tombstones = 0;
}
/**
* Creates a new, empty table with the given capacity.
*/
private void initializeTable(int capacity) {
this.table = new Object[capacity * 2];
this.mask = table.length - 1;
this.clean = 0;
this.maximumLoad = capacity * 2 / 3; // 2/3
}
//最後調用Values實例的put方法完成了數據插入到map中,可以清晰看到key為ThreadLocal類實例,value就是set方法傳進來的數據。
//下面是Values類的put方法處理邏輯,看到for循環時候,尋找插入位置時候先匹配到key,而key存放的位置比較特殊在數組下標的0 ,2, 4 ,6 ... 2n;這些位置,value存放位置在1,3,5,7...2n+1這些位置。
void put(ThreadLocal key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
//index是尋找key存放的下標, key.hash & mask尋找循環的起止位置,mask是table.length-1,默認是31,key.hash & mask計算後使得index一定指向key的下標。next()方法是對index加2操作。
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
ThreadLocal數據獲取流程
//get獲取數據
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);//獲取到當前線程的本地變量map
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;//計算這個鍵值對存放的位置
if (this.reference == table[index]) {
return (T) table[index + 1];//返回結果
}
} else {
values = initializeValues(currentThread);//如果當前線程沒有本地變量,初始化一個空的
}
return (T) values.getAfterMiss(this);//如果確實返回一個默認值。
}
首先得到一個Values對象,然後求出table數組ThreadLocal實例reference屬性的下標。前文說過:ThradLocal對象(key)和相對應的value放在table數組連續的位置中。 也就是table被設計為下標為0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。現在得到index後再index+1就是value在table數組中的下標。即value=table[index+1];return value即可。
什麼是Context?一個Context意味著一個場景,一個場景就是我們和軟件進行交互的一個過程。比如當你使用微信的時候,場景包括聊天界面、通訊錄、朋友圈,以及背後的一些
之前幫別人寫了一個不是那麼優美的圓形進度條,效果圖大家也看過了。但是後某人不滿意,說原應用是倒計時時間最後5s,才開始顯示數字的,同時轉完一圈需要的時間只能是30s左右。
接觸微信支付之前聽說過這是一個坑,,,心裡已經有了准備。。。我以為我沒准跳坑出不來了,沒有想到我填上了,調用成功之後我感覺公司所有的同事都是漂亮的,隔著北京的大霧霾我仿佛
Android中的TextView控件默認是做不到兩端對齊的,都是左對齊。可能的原因是安卓默認數字、字母不能為第一行以後每行的開頭字符,因為數字、字母為半角字符,還有就是