1. 什么是線程
線程(thread)是指一個任務從頭至尾的執(zhí)行流仅淑,線程提供一個運行任務的機制称勋,對于java而言,一個程序中可以并發(fā)的執(zhí)行多個線程漓糙,這些線程可以在多處理器系統(tǒng)上同時運行铣缠。當程序作為一個應用程序運行時,java解釋器為main()方法啟動一個線程昆禽。
2. 線程和進程
區(qū)別和聯(lián)系:
1. 進程中可以包含多個線程蝗蛙,線程必須存在于某個進程實體中 2. 進程在進行上下文切換時由于要切換頁表,往往伴隨者頁調(diào) 度醉鳖,因此開銷比較大捡硅,而線程在進行上下文切換時,由于僅 涉及與自身相關的寄存器狀態(tài)和棧的信息(線程的上下文環(huán) 境主要包含寄存器的值盗棵、程序計數(shù)器壮韭、棧指針)北发,因此開銷 比較小喷屋; 3. 系統(tǒng)在運行時會為每個進程分配不同的內(nèi)存區(qū)域琳拨,但是不會 為線程分配內(nèi)存,同一個進程中的各個線程共享該進程的內(nèi) 存區(qū)域屯曹。 4. 與進程的控制表PCB相似狱庇,線程也有自己的控制表TCB,但是 TCB中所保存的線程狀態(tài)比PCB表中少多了恶耽。 5. 獨立進程間的通信要與核心交互密任,而由于同一進程中的線程 共享內(nèi)存,它們之間的通信就不需要調(diào)用核心偷俭。
3. 線程創(chuàng)建
- 需要從Java.lang.Thread類派生一個新的線程類浪讳,重載它的run()方法;
- 實現(xiàn)Runnalbe接口涌萤,重載Runnalbe接口中的run()方法淹遵。
兩者區(qū)別:
使用實現(xiàn)Runnable接口方式創(chuàng)建線程可以共享同一個目標對象(TreadDemo1 tt=new TreadDemo1();),實現(xiàn)了多個相同線程處理同一份資源.而繼承Thread創(chuàng)建線程的方式形葬,new出了兩個任務類對象合呐,有各自的成員變量,相互之間不干擾笙以。
一淌实、采用繼承Thread類方式:
(1)優(yōu)點:編寫簡單,如果需要訪問當前線程猖腕,無需使用Thread.currentThread()方法拆祈,直接使用this,即可獲得當前線程倘感。
(2)缺點:因為線程類已經(jīng)繼承了Thread類放坏,所以不能再繼承其他的父類。
二老玛、采用實現(xiàn)Runnable接口方式:
(1)優(yōu)點:線程類只是實現(xiàn)了Runable接口淤年,還可以繼承其他的類。在這種方式下蜡豹,可以多個線程共享同一個目標對象麸粮,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU代碼和數(shù)據(jù)分開镜廉,形成清晰的模型弄诲,較好地體現(xiàn)了面向?qū)ο蟮乃枷搿?br> (2)缺點:編程稍微復雜,如果需要訪問當前線程娇唯,必須使用Thread.currentThread()方法齐遵。
4. 線程的生命周期
(1)生命周期的五種狀態(tài)
新建(new Thread)
當創(chuàng)建Thread類的一個實例(對象)時寂玲,此線程進入新建狀態(tài)(未被啟動)。
例如:Thread t1=new Thread();
就緒(runnable)
線程已經(jīng)被啟動梗摇,正在等待被分配給CPU時間片拓哟,也就是說此時線程正在就緒隊列中排隊等候得到CPU資源。例如:t1.start();
運行(running)
線程獲得CPU資源正在執(zhí)行任務(run()方法)留美,此時除非此線程自動放棄CPU資源或者有優(yōu)先級更高的線程進入彰檬,線程將一直運行到結束伸刃。
死亡(dead)
當線程執(zhí)行完畢或被其它線程殺死谎砾,線程就進入死亡狀態(tài),這時線程不可能再進入就緒狀態(tài)等待執(zhí)行捧颅。
自然終止:正常運行run()方法后終止
異常終止:調(diào)用stop()方法讓一個線程終止運行
堵塞(blocked)
由于某種原因?qū)е抡谶\行的線程讓出CPU并暫停自己的執(zhí)行景图,即進入堵塞狀態(tài)。
正在睡眠:用sleep(long t) 方法可使線程進入睡眠方式碉哑。一個睡眠著的線程在指定的時間過去可進入就緒狀態(tài)挚币。
正在等待:調(diào)用wait()方法。(調(diào)用notify()方法回到就緒狀態(tài))
被另一個線程所阻塞:調(diào)用suspend()方法扣典。(調(diào)用resume()方法恢復)
5. 線程安全
1). 什么是線程安全?線程安全是怎么完成的(原理)?
線程安全就是說多線程訪問同一代碼妆毕,不會產(chǎn)生不確定的結果。編寫線程安全的代碼是低依靠線程同步贮尖。
2). 線程安全的類:
StringBuffer:線程安全的可變字符序列笛粘。一個類似于 String的字符串緩沖區(qū),但不能修改湿硝。
Vector:Vector類可以實現(xiàn)可增長的對象數(shù)組薪前。
Hashtable:此類實現(xiàn)一個哈希表,該哈希表將鍵映射到相應的值关斜。任何非 null對象都可以用作鍵或值示括。
API 搜索Collections,查看方法
A:public static <T> Collection<T> synchronizedCollection(Collection<T> c):返回指定 collection 支持的同步(線程安全的)collection痢畜。
B:public static <T> List<T> synchronizedList(List<T> list):返回指定列表支持的同步(線程安全的)列表垛膝。
C:public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m):返回由指定映射支持的同步(線程安全的)映射。
D:public static <T> Set<T> synchronizedSet(Set<T> s):返回指定 set 支持的同步(線程安全的)set丁稀。
E:public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m):返回指定有序映射支持的同步(線程安全的)有序映射吼拥。
F:public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s):返回指定有序 set 支持的同步(線程安全的)有序 set。
6.線程鎖
1. synchronized
把代碼塊聲明為 synchronized二驰,有兩個重要后果扔罪,通常是指該代碼具有 原子性(atomicity)和 可見性(visibility)。
1 原子性
原子性意味著個時刻桶雀,只有一個線程能夠執(zhí)行一段代碼矿酵,這段代碼通過一個monitor object保護唬复。從而防止多個線程在更新共享狀態(tài)時相互沖突。
2 可見性
可見性則更為微妙全肮,它要對付內(nèi)存緩存和編譯器優(yōu)化的各種反常行為敞咧。它必須確保釋放鎖之前對共享數(shù)據(jù)做出的更改對于隨后獲得該鎖的另一個線程是可見的 。
作用:如果沒有同步機制提供的這種可見性保證辜腺,線程看到的共享變量可能是修改前的值或不一致的值休建,這將引發(fā)許多嚴重問題。
原理:當對象獲取鎖時评疗,它首先使自己的高速緩存無效测砂,這樣就可以保證直接從主內(nèi)存中裝入變量。 同樣百匆,在對象釋放鎖之前砌些,它會刷新其高速緩存,強制使已做的任何更改都出現(xiàn)在主內(nèi)存中加匈。 這樣存璃,會保證在同一個鎖上同步的兩個線程看到在 synchronized 塊內(nèi)修改的變量的相同值。
一般來說雕拼,線程以某種不必讓其他線程立即可以看到的方式(不管這些線程在寄存器中纵东、在處理器特定的緩存中,還是通過指令重排或者其他編譯器優(yōu)化)啥寇,不受緩存變量值的約束偎球,但是如果開發(fā)人員使用了同步,那么運行庫將確保某一線程對變量所做的更新先于對現(xiàn)有synchronized塊所進行的更新示姿,當進入由同一監(jiān)控器(lock)保護的另一個synchronized塊時甜橱,將立刻可以看到這些對變量所做的更新。類似的規(guī)則也存在于volatile變量上栈戳。
——volatile只保證可見性岂傲,不保證原子性!
synchronize的限制
synchronized是不錯子檀,但它并不完美镊掖。它有一些功能性的限制:
- 它無法中斷一個正在等候獲得鎖的線程;
- 也無法通過投票得到鎖褂痰,如果不想等下去亩进,也就沒法得到鎖;
- 同步還要求鎖的釋放只能在與獲得鎖所在的堆棧幀相同的堆棧幀中進行缩歪,多數(shù)情況下归薛,這沒問題(而且與異常處理交互得很好),但是,確實存在一些非塊結構的鎖定更合適的情況主籍。
2.ReentrantLock
Java.util.concurrent.lock 中的Lock框架是鎖定的一個抽象习贫,它允許把鎖定的實現(xiàn)作為 Java 類,而不是作為語言的特性來實現(xiàn)千元。這就為Lock的多種實現(xiàn)留下了空間苫昌,各種實現(xiàn)可能有不同的調(diào)度算法、性能特性或者鎖定語義幸海。
ReentrantLock類實現(xiàn)了Lock祟身,它擁有與synchronized 相同的并發(fā)性和內(nèi)存語義,但是添加了類似鎖投票物独、定時鎖等候和可中斷鎖等候的一些特性袜硫。此外,它還提供了在激烈爭用情況下更佳的性能议纯。(換句話說父款,當許多線程都想訪問共享資源時,JVM 可以花更少的時候來調(diào)度線程瞻凤,把更多時間用在執(zhí)行線程上。
class Outputter1 {
private Lock lock = new ReentrantLock();// 鎖對象
public void output(String name) {
lock.lock(); // 得到鎖
try {
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
} finally {
lock.unlock();// 釋放鎖
}
}
}
區(qū)別:
需要注意的是世杀,用sychronized修飾的方法或者語句塊在代碼執(zhí)行完之后鎖自動釋放阀参,而是用Lock需要我們手動釋放鎖,所以為了保證鎖最終被釋放(發(fā)生異常情況)瞻坝,要把互斥區(qū)放在try內(nèi)蛛壳,釋放鎖放在finally內(nèi)!所刀!
3.讀寫鎖ReadWriteLock
上例中展示的是和synchronized相同的功能衙荐,那Lock的優(yōu)勢在哪里?
例如一個類對其內(nèi)部共享數(shù)據(jù)data提供了get()和set()方法浮创,如果用synchronized忧吟,則代碼如下:
class syncData {
private int data;// 共享數(shù)據(jù)
public synchronized void set(int data) {
System.out.println(Thread.currentThread().getName() + "準備寫入數(shù)據(jù)");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
System.out.println(Thread.currentThread().getName() + "寫入" + this.data);
}
public synchronized void get() {
System.out.println(Thread.currentThread().getName() + "準備讀取數(shù)據(jù)");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "讀取" + this.data);
}
}
然后寫個測試類來用多個線程分別讀寫這個共享數(shù)據(jù):
public static void main(String[] args) {
// final Data data = new Data();
final syncData data = new syncData();
// final RwLockData data = new RwLockData();
//寫入
for (int i = 0; i < 3; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) {
data.set(new Random().nextInt(30));
}
}
});
t.setName("Thread-W" + i);
t.start();
}
//讀取
for (int i = 0; i < 3; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) {
data.get();
}
}
});
t.setName("Thread-R" + i);
t.start();
}
}
運行結果:
Thread-W0準備寫入數(shù)據(jù)
Thread-W0寫入0
Thread-W0準備寫入數(shù)據(jù)
Thread-W0寫入1
Thread-R1準備讀取數(shù)據(jù)
Thread-R1讀取1
Thread-R1準備讀取數(shù)據(jù)
Thread-R1讀取1
Thread-R1準備讀取數(shù)據(jù)
Thread-R1讀取1
Thread-R1準備讀取數(shù)據(jù)
Thread-R1讀取1
Thread-R1準備讀取數(shù)據(jù)
Thread-R1讀取1
Thread-R2準備讀取數(shù)據(jù)
Thread-R2讀取1
Thread-R2準備讀取數(shù)據(jù)
Thread-R2讀取1
Thread-R2準備讀取數(shù)據(jù)
Thread-R2讀取1
Thread-R2準備讀取數(shù)據(jù)
Thread-R2讀取1
Thread-R2準備讀取數(shù)據(jù)
Thread-R2讀取1
Thread-R0準備讀取數(shù)據(jù) //R0和R2可以同時讀取,不應該互斥斩披!
Thread-R0讀取1
Thread-R0準備讀取數(shù)據(jù)
Thread-R0讀取1
Thread-R0準備讀取數(shù)據(jù)
Thread-R0讀取1
Thread-R0準備讀取數(shù)據(jù)
Thread-R0讀取1
Thread-R0準備讀取數(shù)據(jù)
Thread-R0讀取1
Thread-W1準備寫入數(shù)據(jù)
Thread-W1寫入18
Thread-W1準備寫入數(shù)據(jù)
Thread-W1寫入16
Thread-W1準備寫入數(shù)據(jù)
Thread-W1寫入19
Thread-W1準備寫入數(shù)據(jù)
Thread-W1寫入21
Thread-W1準備寫入數(shù)據(jù)
Thread-W1寫入4
Thread-W2準備寫入數(shù)據(jù)
Thread-W2寫入10
Thread-W2準備寫入數(shù)據(jù)
Thread-W2寫入4
Thread-W2準備寫入數(shù)據(jù)
Thread-W2寫入1
Thread-W2準備寫入數(shù)據(jù)
Thread-W2寫入14
Thread-W2準備寫入數(shù)據(jù)
Thread-W2寫入2
Thread-W0準備寫入數(shù)據(jù)
Thread-W0寫入4
Thread-W0準備寫入數(shù)據(jù)
Thread-W0寫入20
Thread-W0準備寫入數(shù)據(jù)
Thread-W0寫入29
現(xiàn)在一切都看起來很好溜族!各個線程互不干擾!等等垦沉。煌抒。讀取線程和寫入線程互不干擾是正常的,但是兩個讀取線程是否需要互不干擾厕倍?寡壮?
對!讀取線程不應該互斥!
我們可以用讀寫鎖ReadWriteLock實現(xiàn):
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class Data {
private int data;// 共享數(shù)據(jù)
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public void set(int data) {
rwl.writeLock().lock();// 取到寫鎖
try {
System.out.println(Thread.currentThread().getName() + "準備寫入數(shù)據(jù)");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
System.out.println(Thread.currentThread().getName() + "寫入" + this.data);
} finally {
rwl.writeLock().unlock();// 釋放寫鎖
}
}
public void get() {
rwl.readLock().lock();// 取到讀鎖
try {
System.out.println(Thread.currentThread().getName() + "準備讀取數(shù)據(jù)");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "讀取" + this.data);
} finally {
rwl.readLock().unlock();// 釋放讀鎖
}
}
}
測試結果:
Thread-W1準備寫入數(shù)據(jù)
Thread-W1寫入9
Thread-W1準備寫入數(shù)據(jù)
Thread-W1寫入24
Thread-W1準備寫入數(shù)據(jù)
Thread-W1寫入12
Thread-W0準備寫入數(shù)據(jù)
Thread-W0寫入22
Thread-W0準備寫入數(shù)據(jù)
Thread-W0寫入15
Thread-W0準備寫入數(shù)據(jù)
Thread-W0寫入6
Thread-W0準備寫入數(shù)據(jù)
Thread-W0寫入13
Thread-W0準備寫入數(shù)據(jù)
Thread-W0寫入0
Thread-W2準備寫入數(shù)據(jù)
Thread-W2寫入23
Thread-W2準備寫入數(shù)據(jù)
Thread-W2寫入24
Thread-W2準備寫入數(shù)據(jù)
Thread-W2寫入24
Thread-W2準備寫入數(shù)據(jù)
Thread-W2寫入17
Thread-W2準備寫入數(shù)據(jù)
Thread-W2寫入11
Thread-R2準備讀取數(shù)據(jù)
Thread-R1準備讀取數(shù)據(jù)
Thread-R0準備讀取數(shù)據(jù)
Thread-R0讀取11
Thread-R1讀取11
Thread-R2讀取11
Thread-W1準備寫入數(shù)據(jù)
Thread-W1寫入18
Thread-W1準備寫入數(shù)據(jù)
Thread-W1寫入1
Thread-R0準備讀取數(shù)據(jù)
Thread-R2準備讀取數(shù)據(jù)
Thread-R1準備讀取數(shù)據(jù)
Thread-R2讀取1
Thread-R2準備讀取數(shù)據(jù)
Thread-R1讀取1
Thread-R0讀取1
Thread-R1準備讀取數(shù)據(jù)
Thread-R0準備讀取數(shù)據(jù)
Thread-R0讀取1
Thread-R2讀取1
Thread-R2準備讀取數(shù)據(jù)
Thread-R1讀取1
Thread-R0準備讀取數(shù)據(jù)
Thread-R1準備讀取數(shù)據(jù)
Thread-R0讀取1
Thread-R2讀取1
Thread-R1讀取1
Thread-R0準備讀取數(shù)據(jù)
Thread-R1準備讀取數(shù)據(jù)
Thread-R2準備讀取數(shù)據(jù)
Thread-R1讀取1
Thread-R2讀取1
Thread-R0讀取1
與互斥鎖定相比况既,讀-寫鎖定允許對共享數(shù)據(jù)進行更高級別的并發(fā)訪問屋群。雖然一次只有一個線程(writer 線程)可以修改共享數(shù)據(jù),但在許多情況下坏挠,任何數(shù)量的線程可以同時讀取共享數(shù)據(jù)(reader 線程)
從理論上講芍躏,與互斥鎖定相比,使用讀-寫鎖定所允許的并發(fā)性增強將帶來更大的性能提高降狠。
在實踐中对竣,只有在多處理器上并且只在訪問模式適用于共享數(shù)據(jù)時,才能完全實現(xiàn)并發(fā)性增強榜配》裎常——例如,某個最初用數(shù)據(jù)填充并且之后不經(jīng)常對其進行修改的 collection蛋褥,因為經(jīng)常對其進行搜索(比如搜索某種目錄)临燃,所以這樣的 collection 是使用讀-寫鎖定的理想候選者。
4.volatile
用volatile修飾的變量烙心,線程在每次使用變量的時候膜廊,都會讀取變量修改后的最的值。volatile很容易被誤用淫茵,用來進行原子性操作爪瓜。
5. 讀寫鎖和互斥鎖
讀寫鎖特點:
1)多個讀者可以同時進行讀
2)寫者必須互斥(只允許一個寫者寫,也不能讀者寫者同時進行)
3)寫者優(yōu)先于讀者(一旦有寫者匙瘪,則后續(xù)讀者必須等待铆铆,喚醒時優(yōu)先考慮寫者)
互斥鎖特點:
一次只能一個線程擁有互斥鎖,其他線程只有等待