JavaEE->多線程2

目錄

一、線程安全(重點)

1.線程安全演示

2.線程不安全的原因

1.線程是搶占式執行的(執行順序是隨機的)

2.多個線程同時修改了同一個變量

3.原子性

4.內存可見性

5.指令重排序(有序性)

二、解決線程不安全的問題

1.鎖的概念

2.synchronized

3.synchronized的特性

4.關于synchronized

5.使用單獨的鎖對象

6.synchronized使用示例

7.synchronized - 監視器鎖monitor lock

7.1synchronized的特性

1.互斥

2.可重入

3.可見性

8.volatile 關鍵字


一、線程安全(重點)

1.線程安全演示

/*** 線程安全演示*/public class Text03 {public static void main(String[] args) throws InterruptedException {// 初始化累加對象Counter counter = new Counter();// 創建兩個線程對一個變量進時累加// 線程1Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});// 線程2Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});// 啟動線程t1.start();t2.start();// 等待線程完成t1.join();t2.join();// 查看運行結果System.out.println("count = " + counter.count);}
}// 專門用來累加的類
class Counter {// 初始值是0public int count = 0;/*** 累加方法*/public void increase () {count++;}
}
//count = 68419

程序運行結果與預期值不一致,而且是一個錯誤的結果,而且我們的邏輯是正確的,這個現象所表現的問題稱為線程安全問題

2.線程不安全的原因

1.線程是搶占式執行的(執行順序是隨機的)

由于線程地執行順序無法人為控制,搶占式執行是造成線程安全問題的主要原因,而且我i們解決不了,完全是CPU自己調度,而且和CPU內核數有關

2.多個線程同時修改了同一個變量

多個線程修改同一個變量,出現線程安全問題
多個線程修改不同變量,不會出行線程安全問題
一個線程修改一個變量,不會出現線程安全問題

3.原子性

要么全部執行,要么全部不執行

寫的count++對應多條CPU指令
1.從內存或寄存器中讀取count的值? ? ?LOAD
2.執行自增? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ADD
3.把計算結果寫回寄存器中? ? ? ? ? ? ? ? ?STORE

CPU執行指令,和代碼沒關系

由于執行CPU指令不是原子性的,導致這三條指令沒有執行完就被CPU調度走了
另外的線程加載到一個原始值
當兩個線程分別自增完成后,把值寫回內存時發生覆蓋現象

4.內存可見性

1.Java線程首先是從主內存讀取變量的值到自己工作內存
2.每個線程都有自己的工作內存,且工作內存間是隔離的
3.線程在自己的工作內存中把自己的值修改完成之后再把修改后的值寫回主內存

以上執行的count++操作,由于是兩個線程在在執行,每個線程都有自己的工作內存,且相互之間不可見,最終導致了線程安全問題

工作內存與線程之間是一一對應的(這是JVM規定的)

外存(磁盤)-->內存(運行過程被加載到內存)--> 寄存器(封裝到CPU中)

工作內存是JAVA層面對物理層面的關于程序所使用的到了寄存器的抽象

?如果通過某種方式讓線程之間可以相互通信,稱之為內存可見性

5.指令重排序(有序性)

?我們寫的代碼在編譯之后可能會與代碼對應的指令執行順序不同,這個過程就是指令重排序

JVM層面可能會重排, CPU執行指令時也可以重排

指令重排序必須要保證程序運行的結果是正確的? 單線程的環境里是沒有任何問題的 指令重排序在邏輯上互不影響

二、解決線程不安全的問題

事務的隔離級別是通過鎖和MVCC機制保證的

1.鎖的概念

線程A拿到了鎖,別的線程如果執行被鎖住的代碼,必須要等到線程A釋放鎖,如果線程A沒有釋放鎖,那么別的線程只能阻塞等待,這個狀態就是BLOCK

先拿鎖 --> 執行代碼 --> 釋放鎖 --> 下一個線程再拿鎖...

2.synchronized

可以為方法加鎖也? ? 可以為代碼加鎖

只解決原子性問題,它所修改的代碼有并行變成了串行

public class Text01 {public static void main(String[] args) throws InterruptedException {// 初始化累加對象Counter counter = new Counter();// 創建兩個線程對一個變量進時累加// 線程1Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});// 線程2Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});// 啟動線程t1.start();t2.start();// 等待線程完成t1.join();t2.join();// 查看運行結果System.out.println("count = " + counter.count);}
}// 專門用來累加的類
class Counter {// 初始值是0public int count = 0;/*** 累加方法*/public synchronized void increase () {count++;}
}
//count = 100000

t1先獲得了鎖,執行方法, 方法執行完成之后其它線程在獲取鎖?
這樣的情況是一個單線程運行狀態
是把多線程轉成了單線程,從而解決線程安全問題

解決方法單線程的執行問題,可以修改代碼塊? 把對共享變量的修改加鎖執行?

由于線程在執行邏輯之前要拿到鎖,當拿到鎖時,上一個線程已經執行完了所有的指令,并把修改的值刷回了主內存,當前線程讀到了永遠都是上一個線程修改后的值

t1釋放鎖之后,也有可能第二次循環時t1先于t2拿到鎖,因為線程時搶占式執行的

3.synchronized的特性

1.保證了原子性(通過加鎖來實現)
2.保證了內存有序性(通過串行執行實現)
3.不保證有序性

4.關于synchronized

1.被synchronized修飾的代碼會變成串行執行
2.synchronized可以修飾方法,也可以修飾代碼塊
3.被synchronized修飾的代碼并不是一次性在CPU執行完,而是中途可能被CPU調度走,當所有指令執行完成之后才會釋放鎖
4.只給一個線程加鎖,也會出現線程不安全問題

public class Text01 {public static void main(String[] args) throws InterruptedException {// 初始化累加對象Counter01 counter = new Counter01();// 創建兩個線程對一個變量進時累加// 線程1Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});// 線程2Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase1();}});// 啟動線程t1.start();t2.start();// 等待線程完成t1.join();t2.join();// 查看運行結果System.out.println("count = " + counter.count);}
}// 專門用來累加的類
class Counter01 {// 初始值是0public int count = 0;/*** 累加方法*/public synchronized void increase () {count++;}public void increase1 () {count++;}
}
//count = 81072

線程獲取鎖:
1.如果只有與一個線程A,那么直接可以獲取鎖,沒有鎖競爭
2.線程A,B共同搶一把鎖的是時候,存在鎖競爭,誰先拿到就先執行自己的邏輯,另一個線程阻塞等待,等到持有鎖的線程釋放所之后,再參與競爭鎖
3.線程A,B競爭的不是同一把所的時候,他們沒有競爭關系

5.使用單獨的鎖對象

public class Text02 {public static void main(String[] args) throws InterruptedException {Counter02  counter = new Counter02();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter.count);}
}class Counter02 {// 初始值為0public static int count = 0;// 單獨定義一個對象作為鎖對象用Object locker = new Object();/*** 累加方法**/public void increase () {// 只定義鎖代碼塊synchronized (locker) {count++;}}
}
// count : 100000

Counter中有一個locker,每創建一個counter都會初始化一個對象內部的成員變量locker

/*** 在多個實例中在使用鎖對象*/public class Text03 {public static void main(String[] args) throws InterruptedException {Counter03  counter1 = new Counter03();Counter03  counter2 = new Counter03();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter1.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter2.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter1.count);}
}class Counter03 {// 初始值為0public static int count = 0;// 單獨定義一個對象作為鎖對象用Object locker = new Object();/*** 累加方法**/public void increase () {// 只定義鎖代碼塊synchronized (locker) {count++;}}
}
//count:95487

每個counter中都有一個locker 兩個線程的鎖對象是不同的,不存在鎖競爭關系

/*** 單個實例中,創建兩個方法,使用同一個鎖對象*/public class Text04 {public static void main(String[] args) throws InterruptedException {Counter04  counter = new Counter04();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase1();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter.count);}
}class Counter04 {// 初始值為0public static int count = 0;// 單獨定義一個對象作為鎖對象用Object locker = new Object();/*** 累加方法**/public void increase () {// 只定義鎖代碼塊synchronized (locker) {count++;}}public void increase1 () {// 只定義鎖代碼塊synchronized (locker) {count++;}}
}
//count:100000

locker是同一個對象,會產生鎖競爭關系

/*** 使用靜態全局變量*/
public class Text05 {public static void main(String[] args) throws InterruptedException {Counter05  counter = new Counter05();Counter05  counter1 = new Counter05();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter1.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter.count);}
}class Counter05 {// 初始值為0public static int count = 0;// 全局變量,屬于類對象static Object locker = new Object();/*** 累加方法**/public void increase () {// 只定義鎖代碼塊synchronized (locker) {count++;}}
}
//count:100000

類對象是全局唯一,產生鎖競爭

public class Text06 {public static void main(String[] args) throws InterruptedException {Counter05  counter = new Counter05();Counter05  counter1 = new Counter05();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter1.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter.count);}
}class Counter06 {// 初始值為0public static int count = 0;/*** 累加方法**/public void increase () {// 只定義鎖代碼塊synchronized (Counter06.class) {count++;}}
}
//count:100000
public class Text07 {public static void main(String[] args) throws InterruptedException {Counter05  counter = new Counter05();Counter05  counter1 = new Counter05();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter1.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter.count);}
}class Counter07 {// 初始值為0public static int count = 0;/*** 累加方法**/public void increase () {// 只定義鎖代碼塊synchronized (String.class) {count++;}}
}
//count:100000

任何一個對象都可以作為鎖對象

只能多個線程訪問的鎖對象是同一個,那么他們就存在競爭關系,否則就沒有競爭關系

6.synchronized使用示例

7.synchronized - 監視器鎖monitor lock

7.1synchronized的特性

1.互斥

一個線程獲取了鎖之后,其他線程必須要阻塞等待
只有當持有鎖的線程把鎖釋放了之后,所有的線程再去競爭鎖

2.可重入
package demo3;public class Text1 {public static void main(String[] args) throws InterruptedException {Counter1 counter1 = new Counter1();Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {counter1.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {counter1.increase();}});t1.start();t2.start();t1.join();;t2.join();System.out.println("count = " + counter1.count);}
}class Counter1 {public static int count = 0;/*** 累加方法*/public synchronized void increase () {increase1();}private synchronized void increase1() {increase2();}private void increase2() {synchronized (this) {count++;}}
}
// count = 10000

3.可見性

通過結果來看達到內存可見性的目的,但是是通過原子性來實現的

8.volatile 關鍵字

package demo3;import java.util.Scanner;/*** 創建兩個線程* 1. 第一個線程, 不停的執行自己的任務* 2. 第二個線程,輸入一個停止標識,使第一個線程退出*/
public class Text2 {// 退出標識static int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "線程啟動...");while (flag == 0) {// 不停的去循環, 處理任務}System.out.println(Thread.currentThread().getName() + "線程退出...");}, "t1");// 啟動線程t1.start();// 輸入停止標識Thread t2 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "'線程啟動...");Scanner scanner = new Scanner(System.in);System.out.println("請輸入一個整數:>");flag = scanner.nextInt();System.out.println(Thread.currentThread().getName() + "線程退出...");}, "t2");// 啟動線程t2.start();}
}
/*
t2'線程啟動...
t1線程啟動...
請輸入一個整數:>
1
t2線程退出...
*/

t2線程正常結束,并且修改了flag變量的值?
但是t1線程沒有結束,整個進程頁沒有結束
結果不及預期,線程安全問題產生

package demo3;import java.util.Scanner;/*** 創建兩個線程* 1. 第一個線程, 不停的執行自己的任務* 2. 第二個線程,輸入一個停止標識,使第一個線程退出*/
public class Text2 {// 退出標識static volatile int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "線程啟動...");while (flag == 0) {// 不停的去循環, 處理任務}System.out.println(Thread.currentThread().getName() + "線程退出...");}, "t1");// 啟動線程t1.start();// 輸入停止標識Thread t2 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "'線程啟動...");Scanner scanner = new Scanner(System.in);System.out.println("請輸入一個整數:>");flag = scanner.nextInt();System.out.println(Thread.currentThread().getName() + "線程退出...");}, "t2");// 啟動線程t2.start();}
}
/*
t2'線程啟動...
t1線程啟動...
請輸入一個整數:>
1
t2線程退出...
t1線程退出...
*/

解決了內存可見性
解決了有序性
不保證原子性

多個線程之間涉及的共享變量,如果只存在修改的邏輯,只管加volatile

面試題:JMM如何實現原子性,可見性,有序性

synchronized實現了原子性,由于是串行從而也實現了可見性
volatile真正實現了內存可見性,有序性(使用了內存屏障)

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

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

相關文章

Flutter TCP通信

啟動TCP服務 Future<void> startServer() async {final server await ServerSocket.bind(InternetAddress.anyIPv4, 12345);print(Server listening on ${server.address}:${server.port});server.listen((Socket socket) {print(Client connected: ${socket.remoteAddr…

flask拆分計劃

兩個啟動鏈接&#xff0c;看日志提示是因為2次啟動&#xff0c;一次是database&#xff0c;一次是xmind2&#xff0c;去掉一次就可以&#xff0c;如何去掉一次&#xff1f; 這里啟動也調用了一次&#xff0c;所以測試環境注釋掉&#xff0c;如下圖&#xff0c;也就調用了一次

【生活】ECMO原理、作用、費用及使用方法

博客目錄 一、ECMO 是什么&#xff1f;二、ECMO 的作用1. 替代肺功能&#xff08;氧合與二氧化碳清除&#xff09;2. 替代心臟功能&#xff08;循環支持&#xff09;3. 為其他治療爭取時間4. 用于心肺復蘇&#xff08;ECPR&#xff09; 三、ECMO 的費用1. 設備使用費2. 耗材費用…

Profinet轉EtherCAT網關模塊怎么用:案例分享

在某制造工廠西門子S7-1200 PLC中&#xff0c;存在一個技術難題&#xff0c;即伺服驅動器與可編程邏輯控制器&#xff08;PLC&#xff09;之間的通訊不兼容問題。具體而言&#xff0c;PLC采用的是PROFINET通訊協議&#xff0c;而伺服EtherCAT協議驅動器則需要EtherCAT協議進行數…

什么是 NLP-NLP基礎知識體系的系統認知

NLP基礎知識體系的系統認知 一、引言 今天的學習內容集中于自然語言處理&#xff08;NLP&#xff09;的基本概念、發展歷程、核心任務及文本表示技術。通過這一學習過程&#xff0c;我對NLP這門學科有了更加系統和深入的認識&#xff0c;并且理解了NLP技術的廣泛應用及其復雜…

數據結構 學習 鏈表 2025年6月14日08點01分

單向鏈表: 線性數據結構 由一系列節點組成 每個節點包含: 數據部分:存儲實際數據 指針部分:儲存指向下一個節點的引用 特點1,每個節點只有一個指向下一個節點的指針 特點2,只能從頭到尾 單向遍歷 特點3,不需要連續的內存空間 特點4,插入和刪除效率高 特點5,隨機訪問 效率低 …

使用 Kubernetes 部署 PHP 留言板應用(含 Redis 架構)

使用 Kubernetes 部署 PHP 留言板應用&#xff08;含 Redis 架構&#xff09; 文章目錄 使用 Kubernetes 部署 PHP 留言板應用&#xff08;含 Redis 架構&#xff09;教程概述技術架構特點 準備工作環境要求 Redis 數據庫部署Redis 主從架構原理創建 Redis 領導者 Deployment部…

MATLAB提供的兩種畫誤差矩陣的函數

MATLAB在統計學和機器學習工具包中提供了兩種畫誤差矩陣&#xff08;Confusion matrix&#xff09;的函數。 figure; plotconfusion(YValidation,YPred)figure; cm confusionchart(YValidation,YPred) cm.Title Confusion Matrix for Validation Data; cm.RowSummary row-n…

【Java學習筆記】泛型

泛型 一、泛型的引出 代碼示例 public class pra {public static void main(String[] args) {ArrayList arrayList new ArrayList();arrayList.add("java");arrayList.add("jack");arrayList.add("jom");arrayList.add(new a());for (Object…

SpringMVC系列(一)(介紹,簡單應用以及路徑位置通配符)

0 引言 作者正在學習SpringMVC相關內容&#xff0c;學到了一些知識&#xff0c;希望分享給需要短時間想要了解SpringMVC的讀者朋友們&#xff0c;想用通俗的語言講述其中的知識&#xff0c;希望與諸位共勉&#xff0c;共同進步&#xff01; 1 SpringMVC介紹 SpringMVC本質上…

Java中如何使用lambda表達式分類groupby

Java中如何使用lambda表達式分類groupby Java中如何使用lambda表達式分類groupby分類問題場景傳統手寫方式lambda使用groupBy()方法一行結束&#xff01;&#xff01;&#xff01;完整代碼 Java中如何使用lambda表達式分類groupby 分類問題場景 比如一群學生根據性別和年齡排…

無人機開發分享——無人機集群基于braft實現長機動態推選算法

在無人機集群項目的算法開發中&#xff0c;推選長機作為集群的動態中心&#xff0c;往往承擔著集群管理、通訊中繼等重要功能。由于通訊鏈路的有限性和任務的實時性需要&#xff0c;需要保證動態長機時刻工作正常&#xff0c;并在異常情況下快速切換新長機。 本文主要分享基于b…

python 解碼 jwt

import base64 import jsondef base64url_decode(base64url_data):# 將URL安全的base64編碼數據轉換為標準的base64編碼數據base64_data base64url_data.replace(-, ).replace(_, /)# 如果數據長度不是4的倍數&#xff0c;則補齊padding_length 4 - len(base64_data) % 4base…

騰訊云TCCA認證考試報名 - TDSQL數據庫交付運維工程師(MySQL版)

數據庫交付運維工程師-騰訊云TDSQL(MySQL版)認證 適合人群&#xff1a; 適合從事TDSQL(MySQL版)交付、初級運維、售前咨詢以及TDSQL相關項目的管理人員。 認證考試 單選*40道多選*20道 成績查詢 70分及以上通過認證&#xff0c;官網個人中心->認證考試 查詢 考試費用&am…

Spring Boot的Security安全控制——認識SpringSecurity!

Spring Boot的Security安全控制 在Web項目開發中&#xff0c;安全控制是非常重要的&#xff0c;不同的人配置不同的權限&#xff0c;這樣的系統才安全。最常見的權限框架有Shiro和Spring Security。Shiro偏向于權限控制&#xff0c;而Spring Security能實現權限控制和安全控制…

深入理解ArrayList:從Java原生實現到手寫一個ArrayList

Java原生ArrayList解析 基本結構 Java的ArrayList是基于數組實現的動態列表&#xff0c;主要特點包括&#xff1a; 動態擴容&#xff1a;當元素數量超過當前容量時&#xff0c;自動擴容&#xff08;通常增加50%&#xff09; 快速隨機訪問&#xff1a;通過索引訪問元素的時間…

【力扣 簡單 C】206. 反轉鏈表

目錄 題目 解法一&#xff1a;迭代 解法二&#xff1a;遞歸 題目 解法一&#xff1a;迭代 struct ListNode* reverse(struct ListNode* head) {struct ListNode* retHead NULL;while (head){struct ListNode* nextNode head->next;head->next retHead;retHead he…

明代大模型:智能重構下的文明再發現

引言&#xff1a;當紫禁城遇見生成式AI 一幅動態的《紫禁城圖卷》正通過全息投影技術演繹永樂年間的宮廷盛景。這個虛實交融的場景&#xff0c;恰似明代大模型技術的隱喻——以人工智能為紐帶&#xff0c;連接起永樂盛世的恢弘氣象與數字時代的文明重構。作為人工智能與歷史學…

推薦使用的Unity插件(行為樹Behavior )

在 Unity 6.0 中使用 Behavior Designer 行為樹插件開發 AI 系統&#xff0c;需結合其核心節點設計、變量管理和代碼控制。以下是詳細指南&#xff0c;整合了最新版本的最佳實踐&#xff1a; &#x1f6e0;? 1. 安裝與基礎配置 安裝插件 通過 Unity Asset Store 安裝 “Behav…

107. Java 繼承 - 總結:方法重寫與隱藏

文章目錄 107. Java 繼承 - 總結&#xff1a;方法重寫與隱藏**詳細解釋&#xff1a;****方法重載** **總結** 107. Java 繼承 - 總結&#xff1a;方法重寫與隱藏 在 Java 中&#xff0c;定義與超類中的方法具有相同簽名的方法時&#xff0c;不同類型的方法之間會有不同的行為。…