榆林做網(wǎng)站的公司免費seo網(wǎng)站推廣在線觀看
本系列會更新我在學(xué)習(xí)juc時的筆記和自己的一些思想記錄。如有問題歡迎聯(lián)系。
并發(fā)編程
進(jìn)程與線程
1.進(jìn)程和線程的概念
程序是靜態(tài)的,進(jìn)程是動態(tài)的
進(jìn)程
- 程序由指令和數(shù)據(jù)組成,但這些指令要運行,數(shù)據(jù)要讀寫,就必須將指令加載至 CPU,數(shù)據(jù)加載至內(nèi)存。在指令運行過程中還需要用到磁盤、網(wǎng)絡(luò)等設(shè)備。進(jìn)程就是用來加載指令、管理內(nèi)存、管理 IO 的
- 當(dāng)一個程序被運行,從磁盤加載這個程序的代碼至內(nèi)存,這時就開啟了一個進(jìn)程。
- 進(jìn)程就可以視為程序的一個實例。大部分程序可以同時運行多個實例進(jìn)程(例如記事本、畫圖、瀏覽器等),也有的程序只能啟動一個實例進(jìn)程(例如網(wǎng)易云音樂、360 安全衛(wèi)士等)
線程
- 一個進(jìn)程之內(nèi)可以分為一到多個線程。
- 一個線程就是一個指令流,將指令流中的一條條指令以一定的順序交給 CPU 執(zhí)行
- Java 中,線程作為最小調(diào)度單位,進(jìn)程作為資源分配的最小單位。 在 windows 中進(jìn)程是不活動的,只是作為線程的容器
進(jìn)程和線程對比
- 進(jìn)程基本上相互獨立的,而線程存在于進(jìn)程內(nèi),是進(jìn)程的一個子集
- 進(jìn)程擁有共享的資源,如內(nèi)存空間等,供其內(nèi)部的線程共享
- 進(jìn)程間通信較為復(fù)雜:同一臺計算機(jī)的進(jìn)程通信稱為 IPC(Inter-process communication)。不同計算機(jī)之間的進(jìn)程通信,需要通過網(wǎng)絡(luò),并遵守共同的協(xié)議,例如 HTTP
- 線程通信相對簡單,因為它們共享進(jìn)程內(nèi)的內(nèi)存,一個例子是多個線程可以訪問同一個共享變量
- 線程更輕量,線程上下文切換成本一般上要比進(jìn)程上下文切換低
2.并行和并發(fā)的概念
并發(fā)
并發(fā)能力:同一時間應(yīng)對多件事情的能力。
單核 cpu 下,線程實際還是 串行執(zhí)行 的。操作系統(tǒng)中有一個組件叫做任務(wù)調(diào)度器,將 cpu 的時間片(windows下時間片最小約為 15 毫秒)分給不同的程序使用,只是由于 cpu 在線程間(時間片很短)的切換非???#xff0c;人類感覺是 同時運行的 ??偨Y(jié)為一句話就是: 微觀串行,宏觀并行 ,一般會將這種 線程輪流使用 CPU 的做法稱為并發(fā), concurrent。
并行
多核 cpu下,每個 核(core) 都可以調(diào)度運行線程,這時候線程可以是并行的。
引用 Rob Pike 的一段描述:
- 并發(fā)(concurrent)是同一時間應(yīng)對(dealing with)多件事情的能力
- 并行(parallel)是同一時間動手做(doing)多件事情的能力
例子:
3.線程基本應(yīng)用
應(yīng)用之異步調(diào)用(案例1)
以調(diào)用方角度來講,如果
- 需要等待結(jié)果返回,才能繼續(xù)運行就是同步
- 不需要等待結(jié)果返回,就能繼續(xù)運行就是異步
1)?設(shè)計
多線程可以讓方法執(zhí)行變?yōu)楫惒降?#xff08;即不要巴巴干等著)比如說讀取磁盤文件時,假設(shè)讀取操作花費了 5 秒鐘,如果沒有線程調(diào)度機(jī)制,這 5 秒 cpu 什么都做不了,其它代碼都得暫停...
//同步
@Slf4j(topic =?"c.Sync")
public?class?Sync {
????public?static?void?main(String[]?args)?{
????????FileReader.read(Constants.MP4_FULL_PATH);
????????log.debug("do other things ...");
????}
}
//異步
@Slf4j(topic =?"c.Async")
public?class?Async {
????public?static?void?main(String[]?args)?{
????????new?Thread(()?->?FileReader.read(Constants.MP4_FULL_PATH)).start();
????????log.debug("do other things ...");
????}
}
2)?結(jié)論
- 比如在項目中,視頻文件需要轉(zhuǎn)換格式等操作比較費時,這時開一個新線程處理視頻轉(zhuǎn)換,避免阻塞主線程
- tomcat 的異步 servlet 也是類似的目的,讓用戶線程處理耗時較長的操作,避免阻塞 tomcat 的工作線程
- ui 程序中,開線程進(jìn)行其他操作,避免阻塞 ui 線程
充分利用多核 cpu 的優(yōu)勢,提高運行效率。想象下面的場景,執(zhí)行 3 個計算,最后將計算結(jié)果匯總。
計算 1 花費 10 ms
計算 2 花費 11 ms
計算 3 花費 9 ms
匯總需要 1 ms
- 如果是串行執(zhí)行,那么總共花費的時間是 10 + 11 + 9 + 1 = 31ms
- 但如果是四核 cpu,各個核心分別使用線程 1 執(zhí)行計算 1,線程 2 執(zhí)行計算 2,線程 3 執(zhí)行計算 3,那么 3 個線程是并行的,花費時間只取決于最長的那個線程運行的時間,即 11ms 最后加上匯總時間只會花費 12ms
注意
需要在多核 cpu 才能提高效率,單核仍然時是輪流執(zhí)行
1)?設(shè)計
@Fork(1)
@BenchmarkMode(Mode.AverageTime)//測試模式,統(tǒng)計程序平均時間
@Warmup(iterations=3)//熱身三次
@Measurement(iterations=5)//五輪測試取平均值
public?class?MyBenchmark {
????static?int[]?ARRAY =?new?int[1000_000_00];
????static?{
????????Arrays.fill(ARRAY,?1);
????}
????@Benchmark
????public?int?c()?throws?Exception?{
????????int[]?array =?ARRAY;
????????FutureTask<Integer>?t1 =?new?FutureTask<>(()->{
????????????int?sum =?0;
????????????for(int?i =?0;?i <?250_000_00;i++)?{
????????????????sum +=?array[0+i];
????????????}
????????????return?sum;
????????});
????????FutureTask<Integer>?t2 =?new?FutureTask<>(()->{
????????????int?sum =?0;
????????????for(int?i =?0;?i <?250_000_00;i++)?{
????????????????sum +=?array[250_000_00+i];
????????????}
????????????return?sum;
????????});
????????FutureTask<Integer>?t3 =?new?FutureTask<>(()->{
????????????int?sum =?0;
????????????for(int?i =?0;?i <?250_000_00;i++)?{
????????????????sum +=?array[500_000_00+i];
????????????}
????????????return?sum;
????????});
????????FutureTask<Integer>?t4 =?new?FutureTask<>(()->{
????????????int?sum =?0;
????????????for(int?i =?0;?i <?250_000_00;i++)?{
????????????????sum +=?array[750_000_00+i];
????????????}
????????????return?sum;
????????});
????????new?Thread(t1).start();
????????new?Thread(t2).start();
????????new?Thread(t3).start();
????????new?Thread(t4).start();
????????return?t1.get()?+?t2.get()?+?t3.get()+?t4.get();
????}
????@Benchmark
????public?int?d()?throws?Exception?{
????????int[]?array =?ARRAY;
????????FutureTask<Integer>?t1 =?new?FutureTask<>(()->{
????????????int?sum =?0;
????????????for(int?i =?0;?i <?1000_000_00;i++)?{
????????????????sum +=?array[0+i];
????????????}
????????????return?sum;
????????});
????????new?Thread(t1).start();
????????return?t1.get();
????}
}
在單核的情況下,多線程和單線程效率基本一致,多線程會有上下文切換的耗時。
在多核的情況下,多線程比單線程就會效率翻倍。
2)?結(jié)論
單核 cpu 下,多線程不能實際提高程序運行效率,只是為了能夠在不同的任務(wù)之間切換,不同線程輪流使用cpu ,不至于一個線程總占用 cpu,別的線程沒法干活。
多核 cpu 可以并行跑多個線程,但能否提高程序運行效率還是要分情況的。
- 有些任務(wù),經(jīng)過精心設(shè)計,將任務(wù)拆分,并行執(zhí)行,當(dāng)然可以提高程序的運行效率。但不是所有計算任務(wù)都能拆分(參考后文的【阿姆達(dá)爾定律】)
- 也不是所有任務(wù)都需要拆分,任務(wù)的目的如果不同,談拆分和效率沒啥意義
IO 操作不占用 cpu,只是我們一般拷貝文件使用的是【阻塞 IO】,這時相當(dāng)于線程雖然不用 cpu,但需要一直等待 IO 結(jié)束,沒能充分利用線程。所以才有后面的【非阻塞 IO】和【異步 IO】優(yōu)化
Java線程
1.創(chuàng)建和運行線程
創(chuàng)建線程對象
// 創(chuàng)建線程對象
Thread?t =?new?Thread()?{
?public?void?run()?{
?// 要執(zhí)行的任務(wù)
?}
};
// 啟動線程,交給任務(wù)調(diào)度器,分配時間片,交給時間片去執(zhí)行。
t.start();
例如:
方法一:直接使用?Thread
@Slf4j(topic =?"c.Test1")
public?class?Test1 {
????public?static?void?test2()?{
????????Thread?t =?new?Thread(()->{?log.debug("running");?},?"t2");
????????t.start();
????}
????public?static?void?test1()?{
????????//匿名內(nèi)部類的寫法
????????Thread?t =?new?Thread(){
????????????@Override
????????????public?void?run()?{
????????????????log.debug("running");
????????????}
????????};
????????//創(chuàng)建線程的時候可以給其指定名稱
????????t.setName("t1");
????????t.start();
????}
}
方法二:使用?Runnable?配合?Thread★
把【線程】和【任務(wù)】(要執(zhí)行的代碼)分開
- Thread 代表線程
- Runnable 可運行的任務(wù)(線程要執(zhí)行的代碼)
Runnable源碼,如果接口中有@FunctionalInterface注解,則可以被lambda簡化。如果一個接口中有多個抽象接口,是沒辦法用lambda簡化的。
@FunctionalInterface
public?interface?Runnable?{
????/**
?????*?When an object implementing interface <code>Runnable</code>?is used
?????*?to create a thread,?starting the thread causes the object's
?????*?<code>run</code>?method to be called in that separately executing
?????*?thread.
?????*?<p>
?????*?The general contract of the method <code>run</code>?is that it may
?????*?take any action whatsoever.
?????*
?????*?@see ????java.lang.Thread#run()
?????*/
????public?abstract?void?run();
}
@Slf4j(topic =?"c.Test2")
public?class?Test2 {
????public?static?void?main(String[]?args)?{
????????//果接口中有@FunctionalInterface注解,則可以被lambda簡化
????????Runnable?r =?()?->?{log.debug("running");};
????????Thread?t =?new?Thread(r,?"t2");
????????t.start();
????}
}
原理之?Thread?與?Runnable?的關(guān)系
分析 Thread 的源碼,理清它與 Runnable 的關(guān)系
小結(jié)
- 方法1 是把線程和任務(wù)合并在了一起,方法2 是把線程和任務(wù)分開了
- 用 Runnable 更容易與線程池等高級 API 配合
- 用 Runnable 讓任務(wù)類脫離了 Thread 繼承體系,更靈活
都是走的線程里的run方法。
方法三:FutureTask 配合?Thread
FutureTask 能夠接收 Callable 類型的參數(shù),用來處理有返回結(jié)果的情況
FutureTask源碼分析:
實現(xiàn)一個RunnableFuture的接口
public?class?FutureTask<V>?implements?RunnableFuture<V>?{
RunnableFuture接口又繼承了Runnable和Future
public?interface?RunnableFuture<V>?extends?Runnable,?Future<V>?{
????/**
?????*?Sets this Future to the result of its computation
?????*?unless it has been cancelled.
?????*/
????void?run();
}
Future里有g(shù)et方法返回任務(wù)執(zhí)行結(jié)果
????V get()?throws?InterruptedException,?ExecutionException;
????/**
?????*?Waits if necessary for at most the given time for the computation
?????*?to complete,?and then retrieves its result,?if available.
?????*
?????*?@param timeout the maximum time to wait
?????*?@param unit the time unit of the timeout argument
?????*?@return the computed result
?????*?@throws CancellationException if the computation was cancelled
?????*?@throws ExecutionException if the computation threw an
?????*?exception
?????*?@throws InterruptedException if the current thread was interrupted
?????*?while waiting
?????*?@throws TimeoutException if the wait timed out
?????*/
????V get(long?timeout,?TimeUnit?unit)
????????throws?InterruptedException,?ExecutionException,?TimeoutException;
Callable源碼分析
Callable可以配合FutureTask讓任務(wù)執(zhí)行完了,將結(jié)果傳給其他線程
能拋出異常
@FunctionalInterface
public?interface?Callable<V>?{
????/**
?????*?Computes a result,?or throws an exception if unable to do so.
?????*
?????*?@return computed result
?????*?@throws Exception if unable to compute a result
?????*/
????V call()?throws?Exception;
}
實例代碼:
// 創(chuàng)建任務(wù)對象
FutureTask<Integer>?task3 =?new?FutureTask<>(()?->?{
?log.debug("hello");
?return?100;
});
// 參數(shù)1 是任務(wù)對象; 參數(shù)2 是線程名字,推薦
new?Thread(task3,?"t3").start();
// 主線程阻塞,同步等待 task 執(zhí)行完畢的結(jié)果
Integer?result =?task3.get();
log.debug("結(jié)果是:{}",?result);
輸出:
19:22:27 [t3] c.ThreadStarter - hello
19:22:27 [main] c.ThreadStarter - 結(jié)果是:100
2.查看進(jìn)程線程的方法
windows
- 任務(wù)管理器可以查看進(jìn)程和線程數(shù),也可以用來殺死進(jìn)程
- tasklist 查看進(jìn)程 (tasklist | findstr java)
- taskkill 殺死進(jìn)程 (taskkill /F /PID 280660)
linux
- ps -fe 查看所有進(jìn)程
- ps -fT -p ?查看某個進(jìn)程(PID)的所有線程
- kill 殺死進(jìn)程
- top 按大寫 H 切換是否顯示線程
- top -H -p ?查看某個進(jìn)程(PID)的所有線程
Java
- jps 命令查看所有 Java 進(jìn)程
- jstack ?查看某個 Java 進(jìn)程(PID)的所有線程狀態(tài)
- jconsole 來查看某個 Java 進(jìn)程中線程的運行情況(圖形界面)可以在window+r里直接打印
jconsole 遠(yuǎn)程監(jiān)控配置
- 需要以如下方式運行你的 java 類
java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -
Dcom.sun.management.jmxremote.port=`連接端口` -Dcom.sun.management.jmxremote.ssl=是否安全連接 -
Dcom.sun.management.jmxremote.authenticate=是否認(rèn)證 java類
不需要就false,ip地址和連接端口在輸入后記得把`去掉
- 修改 /etc/hosts 文件將 127.0.0.1 映射至主機(jī)名
如果要認(rèn)證訪問,還需要做如下步驟
- 復(fù)制 jmxremote.password 文件
- 修改 jmxremote.password 和 jmxremote.access 文件的權(quán)限為 600 即文件所有者可讀寫
- 連接時填入 controlRole(用戶名),R&D(密碼)
3.線程運行的原理
棧與棧幀
ava Virtual Machine Stacks (Java 虛擬機(jī)棧)
我們都知道 JVM 中由堆、棧、方法區(qū)所組成,其中棧內(nèi)存是給誰用的呢?其實就是線程,每個線程啟動后,虛擬機(jī)就會為其分配一塊棧內(nèi)存。
- 每個棧由多個棧幀(Frame)組成,對應(yīng)著每次方法調(diào)用時所占用的內(nèi)存
- 每個線程只能有一個活動棧幀,對應(yīng)著當(dāng)前正在執(zhí)行的那個方法
線程上下文切換(Thread Context Switch)
因為以下一些原因?qū)е?cpu 不再執(zhí)行當(dāng)前的線程,轉(zhuǎn)而執(zhí)行另一個線程的代碼
- 線程的 cpu 時間片用完
- 垃圾回收
- 有更高優(yōu)先級的線程需要運行
- 線程自己調(diào)用了 sleep、yield、wait、join、park、synchronized、lock 等方法
當(dāng) Context Switch 發(fā)生時,需要由操作系統(tǒng)保存當(dāng)前線程的狀態(tài),并恢復(fù)另一個線程的狀態(tài),Java 中對應(yīng)的概念就是程序計數(shù)器(Program Counter Register),它的作用是記住下一條 jvm 指令的執(zhí)行地址,是線程私有的
- 狀態(tài)包括程序計數(shù)器、虛擬機(jī)棧中每個棧幀的信息,如局部變量、操作數(shù)棧、返回地址等
- Context Switch 頻繁發(fā)生會影響性能
4.線程
4.1start與run
調(diào)用run
public?static?void?main(String[]?args)?{
?Thread?t1 =?new?Thread("t1")?{
?@Override
?public?void?run()?{
?log.debug(Thread.currentThread().getName());
?FileReader.read(Constants.MP4_FULL_PATH);
?}
?};
?t1.run();
?log.debug("do other things ...");
}
輸出:
是主線程main來調(diào)用run方法,程序仍在 main 線程運行, FileReader.read() 方法調(diào)用還是同步的
19:39:14?[main]?c.TestStart?-?main
19:39:14?[main]?c.FileReader?-?read [1.mp4]?start ...
19:39:18?[main]?c.FileReader?-?read [1.mp4]?end ...?cost:?4227?ms
19:39:18?[main]?c.TestStart?-?do?other things ...
調(diào)用?start
t1.start();
輸出:
19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms
程序在 t1 線程運行, FileReader.read() 方法調(diào)用是異步的
小結(jié):
- 直接調(diào)用 run 是在主線程中執(zhí)行了 run,沒有啟動新的線程
- 使用 start 是啟動新的線程,通過新的線程間接執(zhí)行 run 中的代碼
4.2sleep與yield
sleep
- 調(diào)用 sleep 會讓當(dāng)前線程從 Running?進(jìn)入 Timed Waiting?狀態(tài)(阻塞)
- 其它線程可以使用 interrupt 方法打斷正在睡眠的線程,這時 sleep 方法會拋出 InterruptedException
- 睡眠結(jié)束后的線程未必會立刻得到執(zhí)行(未必能立刻獲得cpu使用權(quán))
- 建議用 TimeUnit 的 sleep 代替 Thread 的 sleep 來獲得更好的可讀性