JUC并發編程 深入學習Java并發編程【上】

?JUC并發編程,深入學習Java并發編程,與視頻每一P對應,全系列6w+字。

P1-5 為什么學+特色+預備知識 進程線程概念

進程:

一個程序被運行,從磁盤加載這個程序的代碼到內存,就開起了一個進程。

進程可以視為程序的一個實例,大部分程序可以同時運行多個實例進程(筆記本,記事本,圖畫,瀏覽器等),也有的程序只能啟動一個實例進程(網易云音樂,360安全衛士等)。

線程:

一個進程內可以分為一到多個線程。

一個線程就是一個指令流,將指令流中的一條條指令以一定的順序交給CPU執行。

Java中線程是最小調度單元,進程作為資源分配的最小單位。在windows中進程是不活動的,知識作為線程的容器。

對比:

進程擁有共享的資源,如內存空間等,供其內部線程共享。

進程間通信較為復雜:同一臺計算機的進程通信稱為IPC。不同計算機之間的進程通信,需要通過網絡,遵守共同的協議。

線程通信簡單,因為共享進程內的內存,多個線程可以訪問同一個共享變量。

線程更輕量,上下文切換成本要比進程上下文切換低。

給項目引入如下pom依賴:

<properties><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target>
</properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency>
</dependencies>

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configurationxmlns="http://ch.qos.logback/xml/ns/logback"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd"><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%date{HH:mm:ss} [%t] %logger - %m%n</pattern></encoder></appender><logger name="c" level="debug" additivity="false"><appender-ref ref="STDOUT"/></logger><root level="ERROR"><appender-ref ref="STDOUT"/></root>
</configuration>

P6 并發并行概念

操作系統任務調度器,可以把CPU時間交給不同線程使用,線程可以輪流使用CPU資源。

假如CPU為單核,同一時間段應對多件事情叫并發。同一時間段處理多件事情的能力。一個人做多件事。

假如CPU為多核,多個核心同時執行任務,叫作并行。同一時間同時做多件事情的能力。多個人做多件事。

P7 線程應用異步調用

同步:需要等待結果返回,才能繼續運行。

異步:不需要等待結果返回,就能繼續運行。

多線程可以讓方法執行變為異步的,不會干巴巴等著,比如讀取磁盤要花費5秒,如果沒有線程調度機制,這5秒什么事情都做不了。

視頻文件要轉換格式操作比較費時,可以開一個新線程處理視頻轉換,避免阻塞主線程。

P8 線程應用提升效率

P9 P10 線程應用提升效率驗證和小結

單核多線程比單核單線程的速度慢。

多核多線程比多核單線程快。

P11 創建線程方法1

源代碼是在如下位置:

一開始默認有一個主線程在運行。

@Slf4j(topic = "c.Test1")
public class Test1 {public static void main(String[] args){Thread t = new Thread(){@Overridepublic void run(){log.debug("running");}};t.setName("t1");t.start();log.debug("running");}
}

P12 創建線程方法2

使用Runnable配合Thread創建線程:

@Slf4j(topic="c.Test2")
public class test2 {public static void main(String[] args) {Runnable r = new Runnable() {@Overridepublic void run() {log.debug("running");}};Thread t = new Thread(r,"t2");t.start();}
}

將任務和線程分離:?

P13 創建線程lambda簡化

@Slf4j(topic="c.Test2")
public class test2 {public static void main(String[] args) {Runnable r =()->{log.debug("running");};Thread t = new Thread(r,"t2");t.start();}
}

超級簡化版:

@Slf4j(topic="c.Test2")
public class test2 {public static void main(String[] args) {Thread t = new Thread(()->{log.debug("running");},"t2");t.start();}
}

P14 創建線程方法1,2-原理

P15 創建線程方法3

FutureTask配合Thread,FutureTask能夠接收Callable類型的參數,用來處理有返回結果的情況。

@Slf4j(topic="c.Test2")
public class Test3 {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {log.debug("running...");Thread.sleep(2000);return 100;}});Thread t1 = new Thread(task,"t1");t1.start();log.debug("{}",task.get());//阻塞住,等待線程,直到線程返回結果}
}

P16 線程運行現象

交替運行。

P17 線程運行windows查看和殺死

查看方式:1.通過任務管理器。2.在控制臺輸入tasklist

找到java進程:

tasklist | findstr java

?查看所有java進程:

jps

殺死某個進程:

taskkill /F /PID PID號

P18 線程運行linux查看和殺死

列出所有正在執行的進程信息:

ps -fe

?用grep關鍵字進行篩選:

ps -fe | grep 關鍵字

查看java進程頁可以用Jps。

殺死某個進程:

kill PID號

查看進程內的線程信息:

top -H -p PID號

P19 線程運行jconsole

輸入win+r,鍵入jconsole,可以打開圖形化界面。

可以遠程連接到服務器監控信息。

P20 線程運行原理棧幀debug

JVM由堆、棧、方法區組成。棧內存是給線程用的,每個線程啟動后,虛擬機會為其分配一塊棧內存。

棧由棧幀組成,對應每次方法調用時所占用的內存。

每個線程只能有一個活動棧幀,對應著當前正在執行的那個方法。

P21 線程運行原理棧幀圖解

?

返回地址對應的是方法區中的方法,局部變量對應的是堆中的對象。

P22?線程運行原理多線程

P23?線程運行原理上下文切換

CPU不再執行當前的線程,轉而執行另一個線程的代碼:

1.線程的CPU時間片用完。

2.垃圾回收。暫停當前所有的工作線程,讓垃圾回收的線程去回收垃圾。

3.有更高優先級的線程需要運行。

4.線程自己調用了sleep,yield,wait,join,park,synchronized,lock等方法。

當Context Switch發生時,需要由操作系統保存當前線程的狀態,并恢復另一個線程的狀態,Java中對應概念是程序計數器,作用是記住下一條jvm指令的執行地址,是線程私有的。

狀態包括程序計數器、虛擬機棧中每個棧幀的信息,如局部變量、操作數棧、返回地址。

P24 常見方法概述

start() 啟動一個新線程,在新的線程運行run方法中的代碼。start方法只能讓線程進入就緒,代碼不一定立即執行(只有等CPU的時間片分配給它才能運行)。每個線程對象的start方法只能調用一次。

join()等待線程運行結束。假如當前的主線程正在等待某個線程執行結束后返回的結果,就可以調用這個join方法。join(long n)表示最多等待n毫秒。

getId()獲得線程id,getName()獲得線程名稱,setName()設置線程名稱,getPriority()獲得優先級,setPriority(int)設置線程優先級,getStatus()獲取線程狀態,isInterupted()判斷是否被打斷,isAlive()判斷線程是否存活,interrupt()打斷線程,interrupted()判斷當前線程是否被打斷。

currentThread()獲取當前正在執行的線程,sleep(long n)讓當前執行的線程休眠n毫秒,休眠時讓出其cpu的時間片給其它線程。

yield()提示線程調度器讓出當前線程對CPU的使用。

P25 常見方法start vs run

用run時是主線程來執行run方法。無法做到異步。

@Slf4j(topic="c.Test4")
public class Test4 {public static void main(String[] args) {Thread t1 = new Thread("t1") {@Overridepublic void run() {log.debug("running...");}};t1.run();}
}

下面是使用start方法啟動,可以異步執行任務。

@Slf4j(topic="c.Test4")
public class Test4 {public static void main(String[] args) {Thread t1 = new Thread("t1") {@Overridepublic void run() {log.debug("running...");}};System.out.println(t1.getState());t1.start();System.out.println(t1.getState());}
}

在new之后start之前是NEW狀態,在start之后是RUNNABLE狀態。?

P26?常見方法sleep狀態

sleep讓線程從running狀態變成time waiting狀態,從運行狀態變到有時限(因為會傳遞一個參數)的等待狀態。

P27?常見方法sleep打斷

正在睡眠的線程可以由其它線程用interrupt方法打斷喚醒。此時睡眠的方法會拋出InterruptException。

程序思路,t1.start執行完,輸出begin,然后休眠,執行t1的run方法輸出enter slee...,然后休眠,1秒到后輸出interrupt,最終t1.interrupt方法被調用,休眠線程立刻被打斷,開始執行wake up....

@Slf4j(topic="c.Test7")
public class Test6 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread("t1") {public void run() {log.debug("enter sleep....");try {Thread.sleep(2000);} catch (InterruptedException e) {log.debug("wake up...");throw new RuntimeException(e);}}};t1.start();log.debug("begin");Thread.sleep(1000);log.debug("interrupt");t1.interrupt();}
}

P28?常見方法sleep可讀性

建議用TimeUnit的sleep代替Thread的sleep來獲得更好的可讀性。

@Slf4j(topic = "c.Test8")
public class Test7 {public static void main(String[] args) throws InterruptedException {log.debug("enter");TimeUnit.SECONDS.sleep(1);log.debug("end");}
}

?

P29?常見方法yield_vs_sleep

1.yield

某個線程調用yield,可以讓出CPU的使用權。

調用yield會讓當前線程從Running進入Runnable就緒狀態,然后調度執行其它線程。

2.sleep

調用sleep會讓當前線程從Running進入Timed Waitring狀態(阻塞)

P30?常見方法線程優先級

線程優先級會提示(hint)調度器優先調度該線程,但它僅僅只是一個提示,調度器可以忽略它。

如果cpu較忙,優先級高的線程會獲得更多的時間片,但cpu如果閑時,優先級幾乎沒作用。

@Slf4j(topic="c.Test4")
public class test4 {public static void main(String[] args) {Runnable task1 =()->{int count=0;for(;;){System.out.println("------>1"+count++);}};Runnable task2 =()->{int count=0;for(;;){//Thread.yield();System.out.println("          ------>2"+count++);}};Thread t1 = new Thread(task1,"t1");Thread t2 = new Thread(task2,"t2");//t1.setPriority(Thread.MIN_PRIORITY);//t2.setPriority(Thread.MAX_PRIORITY);t1.start();t2.start();}
}

P31?常見方法sleep應用

在沒有利用cpu來計算時,不要讓while(true)空轉浪費cpu,這時可以使用yield或sleep來讓出cpu的使用權給其它程序。

可以用wait或者條件變量達到類似的效果。但需要加鎖,并且需要設置相應的喚醒操作,一般適用于要進行同步的場景。sleep適合無鎖同步的場景。

P32?常見方法join

join等待某個線程執行結束。

下面這個例子因為t1線程睡了1秒,對r的更改不會發生,主線程會直接輸出r的結果r=0。此時若想讓r=10,則需要在t1.start()的下面加上t1.join()表示等待t1執行結束返回結果,主線程再執行。

@Slf4j(topic="c.Test5")
public class test5 {static int r=0;public static void main(String[] args) throws InterruptedException{test1();}public static void test1() throws InterruptedException{log.debug("開始");Thread t1 = new Thread(()->{log.debug("開始");sleep(1);log.debug("結束");r=10;},"t1");t1.start();t1.join();log.debug("結果為:{}",r);log.debug("結果");}
}

P33?常見方法join同步應用

需要等待結果返回,才能繼續運行是同步。

不需要等待結果返回,就能繼續運行是異步。

@Slf4j(topic = "c.TestJoin")
public class TestJoin {static int r = 0;static int r1 = 0;static int r2 = 0;public static void main(String[] args) throws InterruptedException {test2();}private static void test2() throws InterruptedException {Thread t1 = new Thread(() -> {sleep(1);r1 = 10;});Thread t2 = new Thread(() -> {sleep(2);r2 = 20;});t1.start();t2.start();long start = System.currentTimeMillis();log.debug("join begin");t1.join();log.debug("t1 join end");t2.join();log.debug("t2 join end");long end = System.currentTimeMillis();log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);}
}

P34?常見方法join限時同步

下面給t1.join()設置了1500毫秒等待時間,因為小于線程睡眠時間,所以沒法能線程蘇醒改變r,輸出結果為r1=0。

@Slf4j(topic = "c.TestJoin")
public class TestJoin {static int r = 0;static int r1 = 0;static int r2 = 0;public static void main(String[] args) throws InterruptedException {test3();}public static void test3() throws InterruptedException {Thread t1 = new Thread(() -> {sleep(2);r1 = 10;});long start = System.currentTimeMillis();t1.start();// 線程執行結束會導致 join 結束log.debug("join begin");t1.join(1500);long end = System.currentTimeMillis();log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);}
}

P35?常見方法interrupt打斷阻塞

如果線程是在睡眠中被打斷會以報錯的形式出現,打斷標記為false。

@Slf4j(topic="c.Test6")
public class test6 {public static void main(String[] args) throws InterruptedException{Thread t1 = new Thread(() -> {log.debug("sleep...");try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}}, "t1");t1.start();Thread.sleep(1000);log.debug("interrupt");t1.interrupt();log.debug("打斷標記:{}",t1.isInterrupted());}
}

P36?常見方法interrupt打斷正常

如果在main方法中調用t1的interrupt方法,t1線程只是會被告知有線程想打斷,不會強制被退出。此時isinterrupted狀態會被設為true,此時可以利用該狀態來讓線程決定是否退出。

@Slf4j(topic="c.Test7")
public class Test7 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{while(true){boolean interrupted = Thread.currentThread().isInterrupted();if(interrupted){log.debug("被打斷了,退出循環");break;}}},"t1");t1.start();Thread.sleep(1000);log.debug("interrupt");t1.interrupt();}
}

P37?設計模式兩階段終止interrupt

在一個線程T1中如何優雅的終止線程T2,這里的優雅指的是給T2一個料理后事的機會。

錯誤思路:

1.使用線程對象的stop方法停止線程。stop方法會真正殺死線程,如果線程鎖住了共享資源,那么當它被殺死后就再也沒有機會釋放鎖,其它線程將永遠無法獲取鎖。

2.使用System.exit(int)方法會直接把方法停止,直接把進程停止。

P38?設計模式兩階段終止interrupt分析

在工作中被打斷,打斷標記是false,會進入到料理后事。

在睡眠是被打斷,會拋出異常,此時打斷標記是true,此時可以重新設置打斷標記為false。

P39?設計模式兩階段終止interrupt實現

@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination{private Thread monitor;public void start(){monitor = new Thread(()->{while(true) {Thread current = Thread.currentThread();if (current.isInterrupted()) {log.debug("料理后事");break;}try {Thread.sleep(1000);log.debug("執行監控記錄");} catch (InterruptedException e) {e.printStackTrace();//重新設置打斷標記current.interrupt();}}});monitor.start();}public void stop(){monitor.interrupt();}
}

P40?設計模式兩階段終止interrupt細節

P41 常見方法interrupt打斷park

@Slf4j(topic="c.Test9")
public class java9 {public static void main(String[] args) throws InterruptedException{test1();}public static void test1() throws InterruptedException{Thread t1 = new Thread(()->{log.debug("park...");LockSupport.park();log.debug("unpark...");log.debug("打斷狀態:{}",Thread.interrupted());LockSupport.park();log.debug("unpark...");},"t1");t1.start();sleep(1);t1.interrupt();}
}

P42?常見方法過時方法

?切忌用stop,suspend方法。

P43?常見方法守護線程

默認情況下,Java進程需要等待所有的線程都運行結束,才會結束。

有一種特殊的線程叫守護線程,只要其它非守護線程執行結束了,即時守護線程的代碼沒有執行完,也會強制結束。

在t1啟動前調用setDaemon方法開啟守護線程,如果主線程運行結束,守護線程也會結束。

@Slf4j(topic="c.Test15")
public class Test10 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{while(true){if(Thread.currentThread().isInterrupted()){break;}}});t1.setDaemon(true);t1.start();Thread.sleep(1000);log.debug("結束");}
}

垃圾回收器線程是一種守護線程。如果程序停止,垃圾回收線程也會被強制停止。

P44?線程狀態五種

初始狀態:在語言層面上創建線程對象,還沒與操作系統中的線程關聯,僅停留在對象層面。比如new了一個Thread對象,但沒調用start方法。

可運行狀態:就緒狀態,線程已經被創建,與操作系統線程關聯,可以由CPU調度器調度執行,可以獲得CPU時間片,但暫時沒獲得時間片。

運行狀態:指獲取了CPU時間片,運行中的狀態。

阻塞狀態:調用了阻塞API,比如BIO讀寫文件,線程不會用到CPU,會導致上下文切換,進入阻塞狀態。等BIO操作完畢,會由操作系統喚醒阻塞的線程,轉換至可運行狀態。

終止狀態:線程已經執行完畢,生命周期結束,不會再轉換為其它狀態。

P45 線程狀態六種

從Java的層面進行描述:

NEW:指被創建,還沒調用Start方法。

RUNNABLE:涵蓋了操作系統層面的可運行、運行、阻塞狀態。

TERMINATED:指被終止狀態,不會再轉化為其它狀態。

3種阻塞的狀態:

BLOCKED(想獲得鎖,但獲得不了,拿不到鎖會陷入block狀態)

WAITING(這個是join等待時的狀態)

TIMED_WAITING(這個是sleep時的狀態,有時限的等待)

P46 線程狀態六種演示

P47 習題應用之統籌分析

P48?習題應用之統籌實現

@Slf4j(topic = "c.Test16")
public class Test11 {public static void main(String[] args) {Thread t1 = new Thread(()->{log.debug("洗水壺");Sleeper.sleep(1);log.debug("燒開水");Sleeper.sleep(5);},"老王");Thread t2 = new Thread(()->{log.debug("洗茶壺");Sleeper.sleep(1);log.debug("洗茶杯");Sleeper.sleep(2);log.debug("拿茶葉");Sleeper.sleep(1);try {t1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("泡茶");},"小王");t1.start();t2.start();}
}

缺點:上面模擬的是小王等老王的水燒開了,小王泡茶,如果反過來要實現老王等小王的茶葉拿過來,老王泡茶呢?代碼最好能適應2種情況。

上面的兩個線程各執行各的,如果要模擬老王把水壺交給小王泡茶,或模擬小王把茶葉交給老王泡茶呢?

P49 第三章小節

P50 本章內容

P51 小故事線程安全問題

多線程下訪問共享資源,因為分時系統導致的數據不一致等安全問題。

P52 上下文切換分析

@Slf4j(topic="c.Test12")
public class Test12 {static int counter = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {counter++;}}, "t1");Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {counter--;}}, "t2");t1.start();t2.start();t1.join();t2.join();log.debug("{}", counter);}
}

結果并不唯一如下:?

?

i++和i--編譯成字節碼不是一條代碼:

?

造成數據不一致的原因是:

某個線程的事情還沒干完,數據還沒來得及寫入,上下文就切換了。根本原因:上下文切換導致指令交錯。

P53 臨界區與競態條件

問題出現在多個線程訪問共享資源。

在多個線程對共享資源讀寫操作時發生指令交錯,出現問題。

一段代碼內如果存在對共享資源的多線程讀寫操作,稱這段代碼為臨界區。

競態條件:多個

P54 上下文切換synchronized解決

為了避免臨界區的競態條件發生,有多種手段可以達到目的。

1.阻塞式的解決方案:synchronized,Lock。

2.非阻塞式的解決方案:原子變量。

本次課使用的是synchronized來解決問題,即對象鎖,它采用互斥的方式來讓同一時刻至多只能有1個線程持有對象鎖,其它線程想獲取對象鎖會被阻塞。這樣保證擁有鎖的線程可以安全的執行臨界區內的代碼,不用擔心上下文切換。

synchronized(對象){臨界區
}
@Slf4j(topic="c.Test12")
public class Test12 {static int counter = 0;static Object lock = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (lock){counter++;}}}, "t1");Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (lock){counter--;}}}, "t2");t1.start();t2.start();t1.join();t2.join();log.debug("{}", counter);}
}

P55?上下文切換synchronized理解

假如t1通過synchronized拿到鎖以后,但是時間片不幸用完了,但這個鎖仍舊是t1的,只有時間片下次重新輪到t1時才能繼續執行。

只有當t1執行完synchronized()塊內的代碼,會釋放鎖。其它線程才能競爭。

P56 上下文切換synchronized理解

當鎖被占用時,就算指令沒執行完上下文切換,其它線程也獲取不到鎖,只有當擁有鎖的線程的所有代碼執行完才能釋放鎖。

?P57?上下文切換synchronized思考

1.把加鎖提到for循環外,相當于5000次for循環都視為一個原子操作。

2.如果線程1加鎖,線程2沒加鎖會導致的情況:線程2去訪問臨界資源時,不會嘗試獲取對象鎖,因此不會被阻塞住,仍然能繼續訪問。

P58 鎖對象面向對象改進

@Slf4j(topic="c.Test12")
public class Test12 {public static void main(String[] args) throws InterruptedException {Room room = new Room();Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (room){room.increment();}}}, "t1");Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (room){room.decrement();}}}, "t2");t1.start();t2.start();t1.join();t2.join();log.debug("{}", room.getCounter());}
}class Room{private int counter = 0;public void increment(){synchronized (this){counter++;}}public void decrement(){synchronized (this){counter--;}}public int getCounter(){synchronized (this){return counter;}}
}

P59 synchronized 加在方法上

synchronized可以加在方法上,相當于鎖住方法。

synchronized加在靜態方法上,相當于所住類。

P60 P61 P62上下文切換synchronized習題1~8

線程1鎖的是類,線程2鎖的是方法。

下面一個鎖類一個鎖方法,鎖的仍然是不同對象,所以會并行執行。

鎖的是同一個Number對象,鎖的是靜態方法,所以鎖的是類。

P63 線程安全分析

P64 線程安全分析局部變量

局部變量的i++只有一行字節碼,不同于靜態變量的i++。

P65 線程安全分析局部變量引用

創建2個線程,然后每個線程去調用method1:

如果method1還沒把數據放入,method2就要取出數據,此時集合為空,會報錯。

將list改為局部變量后,放到方法內:

list是局部變量,每個線程會創建不同實例,沒有共享。

method2和method3的參數從method1中傳遞過啦,與method1中引用同一個對象。

P66 線程安全分析?局部變量暴露引用

因為下面ThreadSafeSubClass繼承了ThreadSafe類,然后重寫了method3方法,導致出現了問題。

必須要改為private和final防止子類去重寫和修改,滿足開閉原則,不讓子類改變父類的行為。

P67? 線程安全分析 局部變量組合調用

常見線程安全的類:

String、Integer、StringBuffer、Random、Vector、Hashtable、java.util.concurrent包下的類

注意:每個方法是原子的,單多個方法的組合不是原子的。

下面Hashtable的get和put單個方法是線程安全的,但二者組合在一起仍然會受到線程上下文的切換的影響。

P68? 線程安全分析 常見類 不可變

String、Integer等都是不可變類,即時被線程共享,因為其內部的狀態不可以改變,因此它們的方法是線程安全的。

String的replace和substring等方法看似可以改變值,實則是創建了一個新的字符串對象,里面包含了截取后的結果。

P69?線程安全分析 實例分析1~3

線程不安全:Map<String,Object> map = new HashMap<>();

線程不安全:Date

下面這段非線程安全:

下面這段非線程安全:

Spring里某一個對象沒有加Scope都是單例的,只有1份,成員變量需要被共享。

P70?線程安全分析 實例分析4~7

下面這個方法是線程安全,因為沒有成員變量,也就是類下沒有定義變量。變量在方法內部,各自都在線程的棧內存中,因此是線程安全的。

下面是線程安全的,因為UserDaoImpl里面沒有可以更改的成員變量(無狀態)。

下面是線程安全的,因為是通過new來創建對象,相當于每個線程拿到的是不一樣的副本。

P71 習題 賣票 讀題

證明方法:余票數和賣出去的票數相等,代表前后一致,沒有線程安全問題。

@Slf4j(topic="c.ExerciseSell")
public class ExerciseTransfer {public static void main(String[] args) throws InterruptedException {//模擬多人買票TicketWindow window = new TicketWindow(100000);//所有線程的集合List<Thread> threadList = new ArrayList<>();//賣出的票數統計List<Integer> amountList = new Vector<>();for(int i=0;i<20000;i++){Thread thread = new Thread(()->{int amount = window.sell(randomAmount());//買票try {Thread.sleep(randomAmount());} catch (InterruptedException e) {throw new RuntimeException(e);}amountList.add(amount);});threadList.add(thread);thread.start();}for (Thread thread :threadList) {thread.join();}log.debug("余票:{}",window.getCount());log.debug("賣出的票數:{}",amountList.stream().mapToInt(i->i).sum());}static Random random = new Random();public static int randomAmount(){return random.nextInt(5)+1;}
}
class TicketWindow{private int count;public TicketWindow(int count){this.count = count;}public int getCount(){return count;}public int sell(int amount){if(this.count >= amount){this.count -= amount;return amount;}else{return 0;}}
}

P72 習題 賣票 測試方法

老師用的是一個測試腳本進行測試。

P73 習題 賣票 解題

臨界區:多個線程對共享變量有讀寫操作。

在sell方法中存在對共享變量的讀寫操作,因此只需要在方法上加synchronized:

P74 習題 轉賬

這道題的難點在于有2個共享變量,一個是a的賬戶中的money,一個是b的賬戶中的money。

@Slf4j(topic="c.ExerciseTransfer")
public class ExerciseTransfer1{public static void main(String[] args) throws InterruptedException {Account a = new Account(1000);Account b = new Account(1000);Thread t1 = new Thread(()->{for (int i = 0; i < 1000; i++) {a.transfer(b,randomAmount());}},"t1");Thread t2 = new Thread(()->{for (int i = 0; i < 1000; i++) {b.transfer(a,randomAmount());}},"t2");t1.start();t2.start();t1.join();t2.join();log.debug("total:{}",(a.getMoney()+b.getMoney()));}static Random random = new Random();public static int randomAmount(){return random.nextInt(100)+1;}
}
class Account {private int money;public Account(int money){this.money = money;}public int getMoney(){return money;}public void setMoney(int money){this.money = money;}public void transfer(Account target,int amount){synchronized(Account.class) {if (this.money >= amount) {this.setMoney(this.getMoney() - amount);//自身余額,減去轉賬金額target.setMoney(target.getMoney() + amount);//對方余額加上轉賬金額}}}}

偷懶的方法加入下面:synchronized(Account.class),相當于鎖住兩個賬戶的臨界資源,缺點是n個賬戶只能有2個賬戶進行交互。

P75 Monitor 對象頭

Klass word是一個指針,指向某個對象從屬的Class,找到類對象,每個對象通過Klass來辨明自己的類型。

在32位虛擬機中:Integer:8+4,int:4。

P76 Monitor 工作原理

Monitor是鎖,Monitor被翻譯為監視器或管程。每個Java對象都可以關聯一個Monitor對象,如果使用synchronized給對象上鎖之后,該對象頭的Mark Word中就被設置指向Monitor對象的指針。

obj是java的對象,Monitor是操作系統提供的監視器,調用synchronized是將obj和Monitor進行關聯,相當于在MarkWord里面記錄Monitor里面的指針地址。

Monitor里面的Owner記錄的是當前哪個線程享有這個資源,EntryList是一個線程隊列,來一個線程就進入到阻塞隊列。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/717317.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/717317.shtml
英文地址,請注明出處:http://en.pswp.cn/news/717317.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JVM相關問題

JVM相關問題 一、Java繼承時父子類的初始化順序是怎樣的&#xff1f;二、JVM類加載的雙親委派模型&#xff1f;三、JDK為什么要設計雙親委派模型&#xff0c;有什么好處&#xff1f;四、可以打破JVM雙親委派模型嗎&#xff1f;如何打破JVM雙親委派模型&#xff1f;五、什么是內…

Spring Cloud Gateway-系統保護Sentinel集成

文章目錄 Sentinel介紹Spring Cloud Gateway集成Sentinelpom依賴Sentinel配置Sentinel集成Nacos作為數據源自定義降級響應 Sentinel介紹 ? 隨著微服務的流行&#xff0c;服務和服務之間的穩定性變得越來越重要。Sentinel 是面向分布式、多語言異構化服務架構的流量治理組件&a…

HTML5:七天學會基礎動畫網頁6

CSS3自定義字體 ①&#xff1a;首先需要下載所需字體 ②&#xff1a;把下載字體文件放入 font文件夾里&#xff0c;建議font文件夾與 css 和 image文件夾平級 ③&#xff1a;引入字體&#xff0c;可直接在html文件里用font-face引入字體&#xff0c;分別是字體名字和路徑 例…

Django官網項目

項目準備 使用VSCODE做IDE。 檢查Python版本。 sudo apt install sudo apt update python3 --version創建項目路徑&#xff0c;創建虛擬環境&#xff0c;創建項目 路徑 \mysite 進入路徑&#xff0c;運行VSCODE 運行 "code ." 創建虛擬環境。 選擇 >python: c…

【推薦算法系列十七】:GBDT+LR 排序算法

排序算法經典中的經典 參考 推薦系統之GBDTLR 極客時間 手把手帶你搭建推薦系統 課程 邏輯回歸&#xff08;LR&#xff09;模型 邏輯回歸&#xff08;LR,Logistic Regression&#xff09;是一種傳統機器學習分類模型&#xff0c;也是一種比較重要的非線性回歸模型&#xff…

AAAI2024-分享若干篇有代碼的優秀論文-圖神經網絡、時間序列預測、知識圖譜、大模型等

圖神經網絡、大模型優化方向系列文章目錄 為了方便大家根據自己的興趣查看自己的研究方向論文&#xff0c;在這里進行了細分。如果有對其中的論文感興趣的&#xff0c;可以查看對應的文章在論文相應的代碼&#xff0c;方便快速上手學習&#xff0c;也可以借助這些代碼的學習快…

16 Educational Codeforces Round 142 (Rated for Div. 2)C. Min Max Sort(遞歸、思維、dp)

C. Min Max Sort 很不錯的一道題目&#xff0c;不過腦電波和出題人每對上&#xff0c; q w q 。 qwq。 qwq。 正難則反。 我們考慮最后一步是怎么操作的。 最后一步一定是對 1 1 1和 n n n進行操作 那么上一步呢&#xff1f; 上一步應該是對 2 2 2和 n ? 1 n-1 n?1 以此類推…

AMD“高級洞察”系列揭示Epyc Naples和Rome原型CPU早期無法啟動問題

AMD在其新的YouTube視頻系列《高級洞察》第一集中&#xff0c;由AMD首席技術官Mark Papermaster擔任主持人&#xff0c;討論了AMD在數據中心領域的突破性進展及其持續增長。然而&#xff0c;AMD在服務器業務的發展并非一帆風順&#xff0c;兩位高管公開討論了早期Epyc Naples和…

【Python】環境管理怎么選擇【virtualenv】【pipenv】【 poetry】【 conda】

前言 剛入門Python&#xff0c;看到PyCharm的環境管理選擇有好幾個選擇&#xff0c;分別是virtualenv、pipenv、venv、conda&#xff0c;只知道這些都可以用來管理Python環境的&#xff0c;但不知道這些環境有什么區別&#xff0c;所以&#xff0c;本文將對這些環境管理進行總…

Avalonia學習(二十九)-儀表

Avalonia制作儀表盤&#xff0c;把控件給大家演示一下&#xff0c;Avalonia有三類自定義控件&#xff0c;分別是用戶控件、模版控件、自主控件。前面已經很多用戶控件了&#xff0c;這個是演示模版控件&#xff0c;另外一種不知道哪種情況下使用。 前端代碼&#xff1a; <…

想從事數據方向職場小白看過來, 數據方面的一些英文解釋

想從事數據方向職場小白看過來&#xff0c;一些英文名詞解釋 文章目錄 想從事數據方向職場小白看過來&#xff0c;一些英文名詞解釋 英文類解釋NoSQL&#xff1a;ESB&#xff1a;ACID &#xff1a;Data Vault&#xff1a;MDM&#xff1a;OLAP&#xff1a;SCD:SBA&#xff1a;MP…

【Django】執行查詢——比較、刪除、復制、批量修改對象

以下述模型為基礎&#xff0c;討論檢索對象的方式方法&#xff1a; from datetime import datefrom django.db import modelsclass Blog(models.Model):name models.CharField(max_length100)tagline models.TextField()def __str__(self):return self.nameclass Author(mod…

【vue】v-if、v-show、v-for 相關所有面試題總結

v-if 和 v-show 的區別 兩個重點【dom】和【生命周期】 v-if 惰性指令&#xff0c;false 不會被編譯、渲染不會存在 DOM 中切換開銷大&#xff0c;需要重新創建元素值變化&#xff0c;使用 v-if 的組件生命周期執行順序 true 變為 false【組件的銷毀】 beforeDestroy / befor…

[Flutter]shared_preferences基本用法以及可視化管理存儲的key和value類型

shared_preferences 是一個Flutter插件&#xff0c;它提供了一種簡單的方式來在應用程序中存儲和獲取持久化的鍵值對數據。它可以用于存儲應用程序的配置信息、用戶偏好設置、登錄狀態等。 使用 shared_preferences 插件&#xff0c;你可以在應用程序中輕松地保存和讀取數據&a…

Java中線程相關的知識

創建子線程的三種方式: 1.自定義線程任務類繼承線程類&#xff0c;以便繼承其功能,重寫其run方法(里面寫自己需要實現的功能)&#xff0c;在main方法調用時創建其任務類實例化對象&#xff0c;然后調用對象的start方法(繼承自父類)&#xff0c;即成功創建線程 優點:創建方式簡…

Pandas DataFrame 基本操作實例100個

Pandas 是一個基于NumPy的數據分析模塊&#xff0c;最初由AQR Capital Management于2008年4月開發&#xff0c;并于2009年底開源。Pandas的名稱來源于“Panel Data”&#xff08;面板數據&#xff09;和“Python數據分析”&#xff08;data analysis&#xff09;。這個庫現在由…

來不及了!大學必須完成的四件事!

老師們常說&#xff0c;上大學就輕松了 其實不然 大學不是人生的終點&#xff0c;而是新的起跑線 不是休息站&#xff0c;而是進入社會的最后沖刺跑道 大學生活苦樂參半&#xff0c;成人世界即將來臨 出了校門&#xff0c;你會發現社會復雜多變&#xff0c;需要不斷學習 稍…

excel中如何使用VLOOKUP和EXACT函數實現區分大小寫匹配數據

在 Excel 中&#xff0c;VLOOKUP 函數默認情況下是不區分大小寫的&#xff1a; 比如下面的案例&#xff0c;直接使用VLOOKUP函數搜索&#xff0c;只會搜索匹配到不區分大小寫的第一個 如果我們想要實現區分大小寫的精確匹配&#xff0c;可以使用 EXACT 函數結合 VLOOKUP 函數 …

【簡說八股】Redisson的守護線程是怎么實現的

Redisson Redisson 是一個 Java 語言實現的 Redis SDK 客戶端&#xff0c;在使用分布式鎖時&#xff0c;它就采用了「自動續期」的方案來避免鎖過期&#xff0c;這個守護線程我們一般也把它叫做「看門狗」線程。 Redission是一個在Java環境中使用的開源的分布式緩存和分布式鎖實…

PyTorch-卷積神經網絡

卷積神經網絡 基本結構 首先解釋一下什么是卷積&#xff0c;這個卷積當然不是數學上的卷積&#xff0c;這里的卷積其實表示的是一個三維的權重&#xff0c;這么解釋起來可能不太理解&#xff0c;我們先看看卷積網絡的基本結構。 通過上面的圖我們清楚地了解到卷積網絡和一般網…