進程概念
進程(英語:process),是計算機中已運行程序的實體。程序本身只是指令,數(shù)據(jù)及其組織形式的描述,進程才是程序的真正運行實例。
線程概念
線程(英語:Thread),是操作系統(tǒng)運算調(diào)度的最小單位,它被包含在進程中,是進程中的實際運行單位。
進程和線程的關(guān)系
在面向線程設(shè)計的系統(tǒng)(如當代大多數(shù)操作系統(tǒng),Linux2.6及更新的版本)中,進程本身不是基本運行單位,而是線程的容器。同一進程中的多條線程,共享該進程中的全部系統(tǒng)資源,如虛擬地址空間,文件描述符和信號處理等等。但同一進程中的多個線程有各自的調(diào)用棧(call stack),自己的寄存器環(huán)境(register context),自己的線程本地存儲(thread-local storage)。
我們可以在 Mac 上打開活動監(jiān)視器,查看進程數(shù)和對應(yīng)的線程數(shù),嘗試理解上面的概念。

還可以看下阮一峰的這個博客,及其下面的部分評論,幫助理解概念。
進程與線程的一個簡單解釋
Java 線程的運行狀態(tài)

- 初始狀態(tài):在其他線程中,新建 Thread 。
- 可運行狀態(tài):調(diào)用該線程的 start 方法,使其變成可運行狀態(tài),在線程池中,等待獲取 CPU 資源執(zhí)行任務(wù),由操作系統(tǒng)調(diào)度。
- 運行中:操作系統(tǒng)調(diào)度該線程,獲得 CPU 資源執(zhí)行任務(wù)。
- 結(jié)束:run() 中的代碼執(zhí)行完畢,main 方法結(jié)束 。
- 阻塞狀態(tài):阻塞狀態(tài)結(jié)束即變成可運行狀態(tài)
- 等待用戶輸入
- sleep 線程睡眠
- thread2.join,等待其他線程 如 thread2 終止后,在恢復(fù)。
- 等待隊列
- synchronized 鎖中,使用 object.wait() 等待隊列,object.notify 喚醒或者到了等待時間,object.notify() 隨機喚醒在此對象監(jiān)視器上其中一個線程,object.notifyAll() 喚醒在此對象監(jiān)視器上的所有線程。
- ReentrantLock Condition await()/signal() 。
- 線程同步(互斥鎖)synchronized,ReentrantLock 等。
- 線程讓步,Thread.yield() 把資源讓給其他線程。線程狀態(tài)由運行中變成可運行。因為是 OS 自己調(diào)度的線程,所以不一定能起到讓步資源的目的。
Thread 基本用法
線程的構(gòu)造方法
Thread()
Thread(Runnable target)
Thread(String name)
Thread(Runnable target, String name)
...
線程的實現(xiàn)方法
- 繼承 Thread ,實現(xiàn) run 方法。
class TestThread1 extends Thread {
@Override
public void run() {
super.run();
System.out.println("testThread1 run end.");
}
}
TestThread1 testThread1 = new TestThread1();
testThread1.start();
- 實現(xiàn) Runnable 接口 和 run方法。
class TestThread2 implements Runnable {
@Override
public void run() {
System.out.println("testThread2 run end.");
}
}
Thread testThread2 = new Thread(new TestThread2(), "t2");
testThread2.start();
- 內(nèi)部類 Runnable。
Thread testThread3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("testThread3 run end.");
}
});
testThread3.start();
啟動線程
TestThread1 testThread1 = new TestThread1();
testThread1.start();
終止線程
void interrupt ()
boolean isInterrupted ()
//示例:
public void test01() {
testThread3 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("t3計數(shù) " + i);
if (testThread3.isInterrupted()) {
break;
}
}
System.out.println("testThread3 run end.");
}
});
testThread3.start();
for (int i = 0; i < 500; i++) {
if(i == 499) {
testThread3.interrupt();
System.out.println("testThread3 interrupt.");
}
}
}
線程優(yōu)先級
void setPriority (int newPriority)
MAX_PRIORITY Constant Value: 10 (0x0000000a)
MIN_PRIORITY Constant Value: 1 (0x00000001)
newPriority 的值在 1 和 10 之間(包括1,10)
線程安全
互斥鎖
互斥鎖(英語:英語:Mutual exclusion,縮寫 Mutex)是一種用于多線程編程中,防止兩條線程同時對同一公共資源(比如全局變量)進行讀寫的機制。該目的通過將代碼切片成一個一個的臨界區(qū)域(critical section)達成。臨界區(qū)域指的是一塊對公共資源進行訪問的代碼,并非一種機制或是算法。一個程序、進程、線程可以擁有多個臨界區(qū)域,但是并不一定會應(yīng)用互斥鎖。
線程鎖 - 參考文章 - JDK 5.0 中更靈活、更具可伸縮性的鎖定機制
synchronized 用法
- 對類對象加鎖
private synchronized void increaseCount1() {
shareData++;
}
private void increaseCount1() {
synchronized (this) {
shareData++;
}
}
- 對其他對象加鎖
private final Object mLock = new Object();
private void increaseCount1() {
synchronized (mLock) {
shareData++;
}
}
- 對類加鎖
private synchronized static void increaseCount1() {
shareData++;
}
private synchronized void increaseCount1() {
synchronized (this.getClass()) {
shareData++;
}
}
synchronized 的優(yōu)點:
使用簡單,易于理解,不必擔(dān)心鎖沒有釋放,JVM 會幫助釋放鎖。
synchronized 的問題:
無法中斷一個等候鎖的線程,無法通過等候鎖的順序來獲得鎖,鎖不被釋放就不會得到鎖。
ReentrantLock 用法
private int shareData;
private ReentrantLock mLock = new ReentrantLock();
public void increaseCount() {
mLock.lock();
try {
shareData++;
} finally {
mLock.unlock();
}
}
oublic void readCount() {
mLock.lock();
try {
return shareData;
} finally {
mLock.unlock();
}
}
ReentrantLock 的優(yōu)點:
實現(xiàn)了 Lock 接口,和 synchronized 相同的并發(fā)性和內(nèi)存語義,還多了輪詢鎖,定時鎖,可中斷鎖等候。在激烈爭用的情況下性能更好。ReentrantLock 默認是不公平鎖,公平鎖可以按照求鎖的順序獲得鎖,但是會耗損性能。
ReentrantLock 的缺點:
要在 finally 中釋放鎖,如果保護的代碼拋出異常,鎖將永遠無法釋放。
synchronized 和 ReentrantLock 該如何選擇:
優(yōu)先使用 synchronized ,除非發(fā)現(xiàn)需要用到 synchronized 不具備的特性(比如時間鎖等候、可中斷鎖等候、無塊結(jié)構(gòu)鎖、多個條件變量或者輪詢鎖)或是在特別激烈爭用的情況下,則適合使用 ReentrantLock。
ReentrantReadWriteLock 具有讀同步優(yōu)化
private int shareData;
private ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
public void increaseCount() {
mLock.writeLock.lock();
try {
shareData++;
} finally {
mLock.writeLock.unlock();
}
}
oublic void readCount() {
mLock.readLock.lock();
try {
return shareData;
} finally {
mLock.readLock.unlock();
}
}
ReentrantReadWriteLock 的特點:
讀鎖之間不互斥,讀鎖和寫鎖之間互斥,寫鎖和寫鎖之間互斥。適用于大量讀操作,少量寫操作的場景。
信號量
信號量(英語:Semaphore)又稱為信號量、旗語,是一個同步對象,用于保持在0至指定最大值之間的一個計數(shù)值。當線程完成一次對該 semaphore 對象等待時,該計數(shù)值減一;當線程完成一次對 semaphore 對象釋放時,計數(shù)值加一。當計數(shù)值為0,則線程等待該 semaphore 對象,直至該 semaphore 對象變成 signaled 狀態(tài)。semaphore 對象的計數(shù)值大于0,為 signaled 狀態(tài);計數(shù)值等于0,為 nonsignaled 狀態(tài)。
public void test02() {
final Semaphore semaphore = new Semaphore(3);
System.out.println("可用的信號量:" + semaphore.availablePermits());
for (int i = 0; i < 50; i++) {
final int flag = i;
Thread testThread = new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("test thread runing..." + flag);
Thread.sleep((long) (Math.random() * 3000));
semaphore.release();
System.out.println("可用的信號量:" + semaphore.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
testThread.start();
}
}
線程協(xié)作
- Object wait¬ify/notifyAll
如果有多個 wait 線程,notify 只隨機喚醒其中之一,notifyAll 會喚醒所有的 wait 線程,被喚醒的線程在鎖池中,待 notify 的線程釋放完鎖之后,所有在鎖池的線程才去競爭鎖。
private final Object mLock = new Object();
private void increaseCount1() {
synchronized (mLock) {
mLock.wait();/mLock.notify();
shareData++;
}
}
- Condition await&signal
private ReentrantLock mLock = new ReentrantLock();
private Condition mCondition = mLock.newCondition();
// 要在鎖的包裹下 lock()-await()/signal()-unlock()
mCondition.await();
mCondition.signal();
線程池
線程池(英語:thread pool):一種線程使用模式。線程過多會帶來調(diào)度開銷,進而影響緩存局部性和整體性能。而線程池維護著多個線程,等待著監(jiān)督管理者分配可并發(fā)執(zhí)行的任務(wù)。這避免了在處理短時間任務(wù)時創(chuàng)建與銷毀線程的代價。線程池不僅能夠保證內(nèi)核的充分利用,還能防止過分調(diào)度??捎镁€程數(shù)量應(yīng)該取決于可用的并發(fā)處理器、處理器內(nèi)核、內(nèi)存、網(wǎng)絡(luò)sockets等的數(shù)量。
使用單線程,固定線程,動態(tài)線程或是自定義的,根據(jù)自己的業(yè)務(wù)需求和使用場景合理選擇。
單線程
可以保障任務(wù)的執(zhí)行順序,隊列是個無界隊列。
static ExecutorService newSingleThreadExecutor()
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 100; i++) {
final int flag = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) (Math.random() * 3000));
System.out.println(Thread.currentThread().getName() + " running ... " + flag);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executorService.execute(runnable);
}
流程圖:

固定線程數(shù)
創(chuàng)建過的線程可以復(fù)用,不過只能創(chuàng)建固定數(shù)量的線程,有需要長時間處理的任務(wù)可以考慮使用。
static ExecutorService newFixedThreadPool(int nThreads)
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
public void testFixedThreadPool() {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 100; i++) {
final int flag = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) (2000));
System.out.println(Thread.currentThread().getName() + " running ... " + flag);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executorService.execute(runnable);
}
}
流程圖:

動態(tài)線程數(shù)
在無空閑線程可用的情況下,可隨時新建線程,創(chuàng)建過的線程可以復(fù)用,池線程數(shù)支持 0-Integer.MAX_VALUE,超過缺省 60 s可以移除線程,適合任務(wù)時間短的異步任務(wù)。
如果是大量的需要長時間處理的任務(wù),因為會創(chuàng)建很多線程,效率會很慢。
static ExecutorService newCachedThreadPool()
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
public void testCachedThreadPool() {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
final int flag = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) (5000));
System.out.println(Thread.currentThread().getName() + " running ... " + flag);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executorService.execute(runnable);
}
}
流程圖:

自定義線程池
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
public void testCustomThreadPool() {
int N = 2;
BlockingQueue<Runnable> blockingDeque = new ArrayBlockingQueue<Runnable>(10);
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(N + 1, N * 2, 1000, TimeUnit.MILLISECONDS, blockingDeque);
for (int i = 0; i < 14; i++) {
final int flag = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) (1000));
System.out.println(Thread.currentThread().getName() + " running ... " + flag);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
poolExecutor.execute(runnable);
}
}
- corePoolSize(核心線程池大小),當提交一個任務(wù)到線程池,如果線程池中的線程數(shù)量小于 corePoolSize,線程池就會創(chuàng)建一個線程,即使有空閑的線程可以執(zhí)行任務(wù)。
- maximumPoolSize(線程池最大大小),如果核心線程池已滿,任務(wù)會添加到任務(wù)隊列,任務(wù)隊列如果也滿了,而當前線程數(shù)小于 maximumPoolSize,則會創(chuàng)建新線程執(zhí)行任務(wù)。
- keepAliveTime(空閑線程存活時間),如果空閑線程 idle 的時間大于 keepAliveTime 則移除該線程。
- unit(時間的單位),單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
-
workQueue(BlockingQueue 阻塞隊列) 的常規(guī)策略:
- 直接切換 (Direct handoffs),SynchronousQueue 就是這種隊列,它將任務(wù)直接交給線程,而不持有。如果沒有線程可以執(zhí)行這個任務(wù),那任務(wù)無法添加到隊列,所以只能創(chuàng)建新線程。這種策略可以避免在處理有內(nèi)部依賴的請求集合時被鎖住。直接切換的策略通常需要沒有限制的 maximumPoolSizes 線程池數(shù),比如 newCachedThreadPool 動態(tài)線程,所以需要設(shè)置 idle 時間來移除空閑超時的線程。這樣可以避免提交的任務(wù)被拒絕執(zhí)行。當要處理的任務(wù)一直比可用的線程多時,線程會無限創(chuàng)建下去。
- 無界隊列(Unbounded queues),LinkedBlockingQueue 屬于無界隊列,newFixedThreadPool 使用的就是這個隊列,當 corePoolSize 的線程都在忙碌的時候,任務(wù)會被添加到這個隊列里等待執(zhí)行。當每個任務(wù)完全獨立于其他任務(wù)的時候,這個策略是合適的,它們的執(zhí)行不會相互干擾,不獨立就會有被鎖住的風(fēng)險。例如,在 Web 服務(wù)中,它有時可以處理突發(fā)的大量請求,但是當處理的任務(wù)比可用的線程一直多時,隊列里的任務(wù)會無限的增多。
- 有界隊列(Bounded queues),ArrayBlockingQueue 屬于有界隊列,有界隊列在配合使用有限 maximumPoolSizes 的時候,有助于防止系統(tǒng)資源耗盡,但 maximumPoolSizes 的大小難以調(diào)整,控制。隊列大小和最大線程池大小需要相互折衷,使用大隊列,小池可以最大限度的減少 CPU 的使用率,系統(tǒng)資源使用和上線文切換的成本,但會導(dǎo)致吞吐量降低。使用小隊列通常需要大的線程池,這可以保持 CPUs 繁忙,但可能會突然遇到無法接受的調(diào)度開銷,這也會降低吞吐量。
- threadFactory,幫助創(chuàng)建線程。
- RejectedExecutionHandler handler(飽和策略),在 Executor 被 shut down 關(guān)閉的時候,新提交的任務(wù)會被拒絕。在使用了有限的最大線程池和有限的隊列時,如果兩項都飽和了,新任務(wù)也會被拒絕。這時,execute 調(diào)用RejectedExecutionHandler 的
rejectedExecution(Runnable, ThreadPoolExecutor)方法,下面有四個預(yù)先定義好的策略供使用:- ThreadPoolExecutor.AbortPolicy(默認的),拒絕時拋出 RejectedExecutionException 異常。
- ThreadPoolExecutor.CallerRunsPolicy ,調(diào)用 execute 的線程自己運行這個任務(wù),這提供了一個簡單的反饋控制機制,會降低新任務(wù)的提交速度。
- ThreadPoolExecutor.DiscardPolicy,丟棄掉無法執(zhí)行的任務(wù)。
- ThreadPoolExecutor.DiscardOldestPolicy,如果 executor 沒有被關(guān)閉,那將丟棄掉隊列最前面的任務(wù),然后在嘗試執(zhí)行(嘗試執(zhí)行,可能還會失敗,會重復(fù)該過程)。
- 自定義策略,實現(xiàn) RejectedExecutionHandler。
流程圖:

從隊列中移除任務(wù)
public void testCustomThreadPool() {
int N = 2;
BlockingQueue<Runnable> blockingDeque = new ArrayBlockingQueue<Runnable>(10);
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(N + 1, N * 2, 1000, TimeUnit.MILLISECONDS, blockingDeque);
for (int i = 0; i < 14; i++) {
final int flag = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) (1000));
System.out.println(Thread.currentThread().getName() + " running ... " + flag);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
poolExecutor.execute(runnable);
}
poolExecutor.getQueue().remove();
poolExecutor.getQueue().remove();
poolExecutor.getQueue().remove();
/*
pool-1-thread-2 running ... 1
pool-1-thread-4 running ... 13
pool-1-thread-3 running ... 2
pool-1-thread-1 running ... 0
pool-1-thread-1 running ... 9
pool-1-thread-2 running ... 6
pool-1-thread-4 running ... 7
pool-1-thread-3 running ... 8
pool-1-thread-4 running ... 12
pool-1-thread-2 running ... 11
pool-1-thread-1 running ... 10
3,4,5 號任務(wù)被刪除,沒有執(zhí)行,該方法刪除隊列前面的任務(wù),0,1,2 已經(jīng)執(zhí)行所以無法刪除。
*/
}
關(guān)閉線程池
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();

獲取任務(wù)的直接結(jié)果
public void testReturnTaskResult() {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<String> future0 = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + " callable running ... 0");
return "0 task finished!";
}
});
Future<?> future1 = executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " runnable running ... 1");
}
});
Future<?> future2 = executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " runnable running ... 2");
}
}, "2 task finished!");
try {
System.out.println("future 0:" + future0.get());
System.out.println("future 1:" + future1.get());
System.out.println("future 2:" + future2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
Future.get() 方法會阻塞當前線程。
聊聊并發(fā)(三)——JAVA線程池的分析和使用 InfoQ
文章中談到如何合理的配置線程池,根據(jù)任務(wù)的性質(zhì),是 CPU 密集型,IO 密集型,還是混合型任務(wù),還有任務(wù)的優(yōu)先級,任務(wù)的執(zhí)行時間,和任務(wù)的依賴性,如數(shù)據(jù)庫連接。