基礎概念
(1) CPU 核心數和線程數的關係
核心數:線程數 = 1:1 使用了超線程技術後: 1:2
多核心:也指單芯片多處理器( Chip Multiprocessors,簡稱CMP),CMP是由美國斯坦福大學提出的,其思想是將大規模並行處理器中的SMP(對稱多處理器)集成到同一芯片內,各個處理器並行執行不同的進程。這種依靠多個CPU同時並行地運行程序是實現超高速計算的一個重要方向,稱為並行處理
多線程: Simultaneous Multithreading.簡稱SMT.讓同一個處理器上的多個線程同步執行並共享處理器的執行資源。
核心數、線程數:目前主流CPU都是多核的。增加核心數目就是為了增加線程數,因為操作系統是通過線程來執行任務的,一般情況下它們是1:1對應關係,也就是說四核CPU一般擁有四個線程。但 Intel引入超線程技術後,使核心數與線程數形成1:2的關係
(2) CPU時間片輪轉機制
又稱RR調度,會導致上下文切換
(3) 上下文切換
即使是單核處理器也支持多線程執行代碼,CPU通過給每個線程分配CPU時間片來實現這個機制。時間片是CPU分配給各個線程的執行時間,因為事件片非常短,所有CPU通過不停地切換線程執行,讓我們感覺多個線程是同時執行,時間片一般 幾十毫秒 (ms)。
CPU通過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個任務。但是,在切換前會保存上一個任務的狀態,以便下次切回這個任務時,可以再加載這個任務的狀態。所以任務 從保存到再加載的過程就是一次上下文切換。
這就像我們同時讀兩本書,當我們在讀一本英文的技術書時,發現某個單詞不認識,於是便打開中英文字典,但是在放下英文技術書之前,大腦必須先記住這本書讀到了多少頁的第多少行,等查完單詞之後,能夠繼續讀這本書。這樣的切換是會影響讀書效率的,同樣上下文
切換也會影響多線程的執行速度。
① 導致原因(線程由於自身原因切出)
1、自發性切換
- Thread.sleep
- wait
- Thread,yield
- Thread,.join
- LockSupport.park
- 發起了IO操作如讀文件
- 等待其他線程持有的鎖
2、非自發性切換
線程由於調度器的原因被迫切出
Ⅰ被切出的時間片用完
Ⅱ比被切出線程的優先級更高的線程需要被運行
② 開銷
Ⅰ直接開銷
操作系統保存和恢復上下文所需的開銷;線程調度器 進行線程調度的開銷
Ⅱ 間接開銷
處理器高速緩存重新加載的開銷;上下文切換也可能導致一級高速緩存中的內容被衝
③ 如何減少上下文 切換
Ⅰ 無鎖併發編程:多線程競爭時,會一起上下文切換,所以多線程處理數據時,可以用一些辦法來避免使用鎖,如將數據的ID按照Hash的算法取模分段,不同的線程處理不同段的數據。
Ⅱ CAS:java的Atomic包 使用CAS算法來更新數據,而不需要加鎖
Ⅲ 使用最少線程:避免創建不需要的線程,比如人物很少,但是創建了很多線程來處理,這樣會造成大量線程都處於等待狀態。
Ⅳ 協程 :在單線程裡實現多任務調度,並在單線程裡維持多個任務間的切換
(4) 什麼是進程和線程
① 進程是程序運行資源分配的最小單位
進程是操作系統進行資源分配的最小單位,其中資源包括:CPU、內存空間、磁盤IO等,同一進程中的多條線程共享該進程中的全部系統資源,而進程和進程之間是相互獨立的。進程是具有一定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。
進程是程序在計算機上的一次執行活動。當你運行一個程序,你就啟動了一個進程。顯然,程序是死的、靜態的,進程是活的、動態的。進程可以分為系統進程和用戶進程。凡是用於完成操作系統的各種功能的進程就是系統進程,它們就是處於運行狀態下的操作系統本身,用戶進程就是所有由你啟動的進程。
②線程是CPU調度的最小單位,必須依賴於進程而存在
線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的、能獨立運行的基本單位。線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。
線程無處不在
任何一個程序都必須要創建線程,特別是Java不管任何程序都必須啟動一個main函數的主線程; Java Web開發裡面的定時任務、定時器、JSP和 Servlet、異步消息處理機制,遠程訪問接口RM等,任何一個監聽事件, onclick的觸發事件等都離不開線程和併發的知識。
(5) 併發和並行
並行:同一時刻,可以同時處理事情的能力
併發:與單位時間相關,在單位時間內可以處理事情的能力
(6) 高併發編程的意義、好處和注意事項
好處:充分利用cpu的資源、加快用戶響應的時間,程序模塊化,異步化
問題:
線程共享資源,存在衝突;
容易導致死鎖;
啟用太多的線程,就有搞垮機器的可能
認識Java裡的線程
2.1 java 啟動線程有哪幾種方式
- 類Thread
- 接口Runnable
- 接口 Callable
<code>public class ThreadDemo {
private static class RunDemo implements Runnable{
@Override
public void run( ) {
}
}
private static class CallableDemo implements Callable<string>{
@Override
public String call( ) throws Exception {
return "hello world";
}
}
public static void main( String[] args ) throws ExecutionException, InterruptedException {
RunDemo runDemo = new RunDemo();
new Thread(runDemo).start();
CallableDemo callableDemo = new CallableDemo();
FutureTask<string> futureTask = new FutureTask<string>(callableDemo);
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}/<string>/<string>/<string>/<code>
2.2 下面我們看下Java中存在的線程
<code>public class MainDemo {
public static void main( String[] args ) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false,false);
for(ThreadInfo threadInfo:threadInfos){
System.out.println("["+threadInfo.getThreadId()+"]"+" " + threadInfo.getThreadName());
}
}
}/<code>
執行結果:
<code>[6] Monitor Ctrl-Break
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main/<code>
6] Monitor Ctrl-Break :
[5] Attach Listener : 獲取當前運行的程序的相關信息,比如內存大小、系統屬性等。
[4] Signal Dispatcher : 分發給我虛擬機信號的線程
[3] Finalizer : 調用我們對象 finalize 方法
[2] Reference Handler : 清除引用的線程
[1] main : 用戶程序的入口
2.2 線程的狀態
2.3 線程的中斷
在java 中,線程中斷是一種重要的線程協作 機制,嚴格 講,線程中斷並不會使線程立即退出,而是給線程發送一個通知,告知目標線程,有人希望你退出,至於目標線程接到通知後如何處理,則完全由目標線程自行決定。
線程中斷的三個方法:
- public void Thread.interrupt() //中斷線程
- public boolean Thread.isInterrupted() //判斷是否被 中斷
- public static boolean Thread.interrupted() // 判斷是否被中斷,並清除當前中斷 狀態
<code>public class InterruptDemo {
private static int i;
public static void main( String[] args ) throws InterruptedException {
Thread t1 = new Thread(()->{
while (true){
System.out.println(i++);
}
});
t1 .start();
Thread.sleep(2000);
t1 .interrupt();/<code>
上面代碼不會被中斷,因為沒有中斷處理邏輯,即使被加上了中斷 狀態,但是這個中斷不會發生作用。
如果希望t1 在中斷後退出,就必須為它增加相應的中斷 處理代碼
<code>public class InterruptDemo {
private static int i;
public static void main( String[] args ) throws InterruptedException {
Thread thread = new Thread(()->{
while (true){
if(Thread.interrupted()){
break;
}
System.out.println(i++);
}
});
thread.start();
Thread.sleep(2000);
thread.interrupt();/<code>
測試結果:無限循環下去
<code>public class InterruptDemo2 {
private static int i;
public static void main( String[] args ) throws InterruptedException {
Thread thread = new Thread(()->{
while (true){
boolean in = Thread.currentThread().isInterrupted();
if(in){
System.out.println("before:"+in);
Thread.interrupted();//設置復位
System.out.println("after:"+Thread.currentThread().isInterrupted());
}
}
});
thread.start();
Thread.sleep(2000);
thread.interrupt();/<code>
測試結果:
before:true
after:false
<code>public class InterruptDemo3 {
private static int i;
public static void main( String[] args ) throws InterruptedException {
Thread thread = new Thread(()->{
while (true){
if(Thread.currentThread().isInterrupted()){
System.out.println("Interrupted");
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// 異常裡會清除中斷標誌位catche
System.out.println("Interrupted When Sleep");
//設置中斷
Thread.currentThread().interrupt();
}
}
});
thread.start();
Thread.sleep(2000);
thread.interrupt();
System.out.println("before:"+thread.isInterrupted());
Thread.sleep(2000);
System.out.println("after:"+thread.isInterrupted());
/<code>
上述代碼,如果不在 catch裡 重新設置中斷,則下面代碼不會執行:
<code> if(Thread.currentThread().isInterrupted()){
System.out.println("Interrupted");
break;
}/<code>
調用一個線程的interrupt() 方法中斷一個線程,並不是強行關閉這個線程,只是跟這個線程打個招呼,將線程的中斷標誌位置為true,線程是否中斷,由線程本身決定。
isInterrupted() 判定當前線程是否處於中斷狀態。
static方法interrupted() 判定當前線程是否處於中斷狀態,同時中斷標誌位改為false。
方法裡如果拋出InterruptedException,線程的中斷標誌位會被複位成false,如果確實是需要中斷線程,要求我們自己在catch語句塊裡再次調用interrupt()。
2.4 守護線程和非守護線程
守護線程的做法:
<code>Thread t = new Thread();
t.setDaemon();
t.start/<code>
閱讀更多 碼農的一天 的文章