1.幾個(gè)重要概念
同步與異步
同步調(diào)用會(huì)等待方法的返回,異步調(diào)用會(huì)瞬間返回,但是異步調(diào)用瞬間返回并不代表你的任務(wù)就完成了,它會(huì)在后臺(tái)起個(gè)線程繼續(xù)進(jìn)行任務(wù)。
阻塞和非阻塞
阻塞和非阻塞通常形容多線程間的相互影響。比如一個(gè)線程占用了臨界區(qū)資源,那么其它所有需要這個(gè)資源的線程就必須在這個(gè)臨界區(qū)中進(jìn)行等待,等待會(huì)導(dǎo)致線程掛起。這種情況就是阻塞。此時(shí),如果占用資源的線程一直不愿意釋放資源,那么其它所有阻塞在這個(gè)臨界區(qū)上的線程都不能工作。非阻塞允許多個(gè)線程同時(shí)進(jìn)入臨界區(qū)。
并發(fā)和并行
并行則是兩個(gè)任務(wù)同時(shí)進(jìn)行,而并發(fā)呢,則是一會(huì)做一個(gè)任務(wù)一會(huì)又切換到另一個(gè)任務(wù)。所以單個(gè)cpu是不能做到并行的,只能是并發(fā)。
臨界區(qū)
臨界區(qū)用來表示一種公共資源或者說是共享數(shù)據(jù),可以被多個(gè)線程使用,但是每一次,只能有一個(gè)線程使用它,一旦臨界區(qū)資源被占用,其他線程要想使用這個(gè)資源,就必須等待。
死鎖
是指兩個(gè)或兩個(gè)以上的線程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法進(jìn)行下去。比如線程1占了B資源,請(qǐng)求A資源,線程2占了A資源,請(qǐng)求B資源。
活鎖
線程1可以使用資源,但它讓其他線程先使用資源;線程2也可以使用資源,但它也讓其他線程先使用資源,于是兩者一直謙讓,都無法使用資源。
2.使用線程

有三種方法可以創(chuàng)建線程
- 實(shí)現(xiàn) Runnable 接口;
- 實(shí)現(xiàn) Callable 接口;
- 繼承 Thread 類。
public class TestRunnable implements Runnable {
public void run() {
}
}
public class TestCallable implements Callable<String> {
public String call() {
return "HI";
}
}
public class TestThread extends Thread {
public void run() {
}
}
實(shí)現(xiàn) Runnable 和 Callable 接口的類只能當(dāng)做一個(gè)可以在線程中運(yùn)行的任務(wù),不是真正意義上的線程,因此最后還需要通過 Thread 來調(diào)用。與 Runnable 相比,Callable 可以有返回值,返回值通過 FutureTask 進(jìn)行封裝。
public static void main(String[] args) {
TestRunnable test1 = new TestRunnable ();
Thread t1= new Thread(test1);
t1.start();
TestCallable test2 = new TestCallable();
FutureTask<String> ft = new FutureTask<>(test2);
Thread t2= new Thread(ft);
t2.start();
System.out.println(ft.get());
TestThread t3 = new TestThread();
t3.start();
}
線程中斷
當(dāng)一個(gè)線程正在運(yùn)行時(shí),另一個(gè)線程調(diào)用對(duì)應(yīng)的 Thread 對(duì)象的 interrupt()方法來中斷它,只是在目標(biāo)線程中設(shè)置一個(gè)標(biāo)志,表示它已經(jīng)被中斷,并立即返回。但實(shí)際上線程并沒有被中斷,還會(huì)繼續(xù)往下執(zhí)行。
線程中斷的3個(gè)方法
- interrupt() 中斷線程
- isInterrupted() 判斷是否被中斷
- interrupted() 判斷是否被中斷,并清除當(dāng)前中斷狀態(tài)
public class TestInterrupt implements Runnable{
public void run(){
try{
System.out.println("in TestInterrupt() - start");
Thread.sleep(20000);
System.out.println("in TestInterrupt() - woke up");
}catch(InterruptedException e){
System.out.println("in TestInterrupt() - interrupted");
}
System.out.println("in TestInterrupt() - leaving");
}
public static void main(String[] args) {
TestInterrupt test= new TestInterrupt();
Thread t = new Thread(test);
t.start();
//主線程休眠2秒,從而確保剛才啟動(dòng)的線程有機(jī)會(huì)執(zhí)行一段時(shí)間
try {
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("in main() - interrupting other thread");
//中斷線程t
t.interrupt();
System.out.println("in main() - leaving");
}
}
最后會(huì)輸出 in TestInterrupt () - leaving。
3.線程間協(xié)作
線程等待與喚醒
調(diào)用 wait() 使得線程被掛起,當(dāng)其他線程調(diào)用 notify() 或者 notifyAll() 來喚醒掛起的線程。它們都屬于Object,而不屬于 Thread。
public class TestWait {
public synchronized void before() {
System.out.println("before");
notifyAll();
}
public synchronized void after() {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after");
}
}
public static void main(String[] args) {
TestWait test = new TestWait ();
new Thread(()->test.after()).start();
new Thread(()->test.before()).start();
}
wait() 和 sleep() 的區(qū)別
- wait() 是 Object 的方法,而 sleep() 是 Thread 的靜態(tài)方法;
- wait() 會(huì)釋放鎖,sleep() 不會(huì)。
java.util.concurrent 類庫中提供了 Condition 類,可調(diào)用 await() 方法使線程等待,其它線程調(diào)用 signal() 或 signalAll() 方法喚醒等待的線程。用法跟上面的類似,只是把同步塊synchronized換成了同步鎖ReentrantLock。
兩者的區(qū)別:
- synchronized 是 JVM 實(shí)現(xiàn)的,而 ReentrantLock 是 JDK 實(shí)現(xiàn)的。
- synchronized 是非公平的,ReentrantLock 默認(rèn)情況下也是非公平的,但是可以實(shí)現(xiàn)公平鎖。
- ReentrantLock 等待可中斷,而 synchronized 不行。
- ReentrantLock 可以同時(shí)綁定多個(gè)條件Condition。
- ReentrantLock 可重入,一個(gè)線程可以反復(fù)得到相同的一把鎖。
public class TestAwait{
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void before() {
lock.lock();
try {
System.out.println("before");
condition.signalAll();
} finally {
lock.unlock();
}
}
public void after() {
lock.lock();
try {
condition.await();
System.out.println("after");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
join()
在線程中調(diào)用另一個(gè)線程的 join() 方法,會(huì)將當(dāng)前線程掛起,而不是忙等待,直到目標(biāo)線程結(jié)束。
public static class TestJoin {
int num=0;
public void printNum(){
for (int i = 0; i < 100; i++){
num=i;
}
}
public static void main(String[] args) {
TestJoin test = new TestJoin ();
Thread t=new Thread(()->test.printNum()).start();
t.join();
System.out.println(test.num);
}
}
4.內(nèi)存模型
主內(nèi)存與工作內(nèi)存
Java 內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)。此處的變量主要是指共享變量。Java 內(nèi)存模型規(guī)定所有的變量都存儲(chǔ)在主內(nèi)存中,而每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了該線程使用到的變量的主內(nèi)存副本拷貝,線程對(duì)變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量。不同線程之間也無法直接訪問對(duì)方工作內(nèi)存中的變量,線程間變量值得傳遞均需要通過主內(nèi)存來完成。

Java 內(nèi)存模型中定義了以下 8 種操作來完成主內(nèi)存與工作內(nèi)存之間交互的實(shí)現(xiàn)細(xì)節(jié)。

- luck(鎖定):作用于主內(nèi)存的變量,它把一個(gè)變量標(biāo)示為一條線程獨(dú)占的狀態(tài)。
- unlock(解鎖):作用于主內(nèi)存的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。
- read(讀取):作用于主內(nèi)存的變量,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)焦ぷ鲀?nèi)存中,以便隨后的 load 動(dòng)作使用。
- load(載入):作用于工作內(nèi)存的變量,它把 read 操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
- use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中的一個(gè)變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的值得字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。
- assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作。
- store(存儲(chǔ)):作用于工作內(nèi)存的變量,它把工作內(nèi)存中的一個(gè)變量的值傳遞到主內(nèi)存中,以便隨后的 write 操作使用。
- write(寫入):作用于主內(nèi)存的變量,它把 store 操作從工作內(nèi)存中得到的變量值放入主內(nèi)存的變量中。
三大特性
- 原子性:指一個(gè)操作是不可中斷的。即使是在多個(gè)線程一起執(zhí)行的時(shí)候,一個(gè)操作一旦開始,就不會(huì)被其它線程干擾。
- 有序性:在并發(fā)時(shí),程序的執(zhí)行可能就會(huì)出現(xiàn)亂序。因?yàn)橹噶钣锌赡鼙恢嘏拧?/li>
- 內(nèi)存可見性:指當(dāng)一個(gè)線程修改了某一個(gè)共享變量的值,其他線程是否能夠立即知道這個(gè)修改。
5.volatile和ThreadLocal
volatile 修飾的成員變量在每次被線程訪問時(shí),都強(qiáng)迫從共享內(nèi)存中重讀該成員變量的值。而且,當(dāng)成員變量發(fā)生變化時(shí),強(qiáng)迫線程將變化值回寫到共享內(nèi)存。這樣在任何時(shí)刻,兩個(gè)不同的線程總是看到某個(gè)成員變量的同一個(gè)值。
volatile 是一種稍弱的同步機(jī)制,在訪問 volatile 變量時(shí)不會(huì)執(zhí)行加鎖操作,也就不會(huì)執(zhí)行線程阻塞,因此 volatilei 變量是一種比 synchronized 關(guān)鍵字更輕量級(jí)的同步機(jī)制。
volatile 雖然保證了內(nèi)存可見性和有序性(添加內(nèi)存屏障的方式來禁止指令重排),但不能保證原子性,所以是線程不安全的。
ThreadLocal是解決線程安全問題一個(gè)很好的思路,ThreadLocal類中有一個(gè)Map,用于存儲(chǔ)每一個(gè)線程的變量副本,Map中元素的鍵為線程對(duì)象,而值對(duì)應(yīng)線程的變量副本,由于Key值不可重復(fù),每一個(gè)“線程對(duì)象”對(duì)應(yīng)線程的“變量副本”,而到達(dá)了線程安全。
public class TestThreadLocal{
//通過匿名內(nèi)部類覆蓋ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
public Integer initialValue(){
return 0;
}
};
public int getNextNum(){
seqNum.set(seqNum.get()+1);
return seqNum.get();
}
public static void main(String[] args) {
TestThreadLocal test = new TestThreadLocal();
new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("thread["+Thread.currentThread().getName()+
"] num["+test.getNextNum()+"]");
}
}) .start();
new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("thread["+Thread.currentThread().getName()+
"] num["+test.getNextNum()+"]");
}
}) .start();
new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("thread["+Thread.currentThread().getName()+
"] num["+test.getNextNum()+"]");
}
}) .start();
}
}
ThreadLocal和線程同步機(jī)制比較
- 同步機(jī)制中,通過對(duì)象的鎖機(jī)制保證同一時(shí)間只有一個(gè)線程訪問變量。這時(shí)該變量是多個(gè)線程共享的。
- ThreadLocal會(huì)為每一個(gè)線程拷貝一份獨(dú)立的變量副本,從而隔離了多個(gè)線程對(duì)數(shù)據(jù)的訪問沖突。
當(dāng)然ThreadLocal并不能替代同步機(jī)制,兩者面向的問題領(lǐng)域不同。
參考資料
高并發(fā)Java
線程通信