一個既簡單又詭異的問題

public class DaYaoGuai {static String s;public static void main(String[] args) {Thread t1 = new Thread(){@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}s="深圳";}};t1.start();Thread t2 = new Thread(){@Overridepublic void run() {while (s==null){System.out.println("空的");//屏蔽掉這一句,就永遠都不會打印出"深圳";有這一句就能打印出"深圳"}System.out.println(s);}};t2.start();}
}

上面的這句?System.out.println("空的") 到底影響了什么?怎么會對結果有那么大影響?

2025年4月21日補充

先說結論:與內存模型、指令優化有關,沒有System.out.println("空的")的話,該線程使用的將會一直是它工作內存里緩存的s,而System.out.println("空的")能觸發同步進而刷新s。

這一代碼存在線程安全方面的問題,因為靜態變量s未被volatile關鍵字修飾。在 Java 里,每個線程都有自己的工作內存,線程操作變量時,先把變量從主內存復制到工作內存,操作完成之后再寫回主內存。線程t1修改了s的值,不過這個修改在主內存里,線程t2工作內存中的s副本可能仍然是null。要是線程t2一直使用工作內存中的s副本,那么while (s == null)這個循環條件始終為真,循環不會結束,也就無法打印出 “深圳”。svolatile關鍵字修飾后,線程t1s的修改會馬上刷新到主內存,線程t2也會立即從主內存讀取s的最新值,這樣就能保證線程t2能正確打印出 “深圳”。

那么,為什么t1修改了s的值后,t2工作內存中的副本不能及時更新?

現代處理器為了提高性能,會采用緩存、指令重排序等優化手段。

  • 緩存機制:處理器會將經常訪問的數據存儲在高速緩存中,以減少對主內存的訪問次數。每個處理器核心都有自己的緩存,當一個線程在某個處理器核心上運行時,它對變量的操作是在該核心的緩存中進行的。當t1線程修改了s的值,這個修改可能只存在于t1所在處理器核心的緩存中,而t2所在處理器核心的緩存中的s值仍然是舊的。
  • 指令重排序:為了提高程序的執行效率,編譯器和處理器可能會對指令進行重排序。在t2線程中,編譯器或處理器可能會對while (s == null)這個循環進行優化,將s的讀取操作緩存起來,不再每次都從主內存中讀取,從而導致t2無法及時獲取到s的最新值。

在t2的while循環中加一句System.out.println("空的"),最終也能打印出“深圳",這又是什么原理?

1. 涉及同步

System.out.println?方法是一個同步方法,它的實現內部使用了?synchronized?塊。在 Java 中,System.out?是?PrintStream?類的一個實例,println?方法的調用會進入一個同步代碼塊:

// PrintStream 類中的 println 方法
public void println(String x) {synchronized (this) {print(x);newLine();}
}

根據 Java 內存模型(JMM),進入同步代碼塊時,線程會從主內存中讀取共享變量的最新值,退出同步代碼塊時,會將工作內存中的數據刷新到主內存。因此,在每次執行System.out.println("空的")?時,線程?t2?都會從主內存中讀取?s?的最新值。也就是,synchronized會讓線程清空自己的緩存,然后重新去主內存拷貝一份副本,這樣,每次System.out.println()執行時其實就是刷新線程變量了。

2.?線程調度和上下文切換

在執行 System.out.println("空的")?時,由于這個操作涉及到 I/O 操作和同步機制,會導致線程?t2?發生上下文切換。上下文切換是指操作系統暫停當前正在執行的線程,保存其狀態,然后選擇另一個線程執行的過程。

在上下文切換過程中,線程?t2?會釋放 CPU 資源,之后再次獲得 CPU 資源繼續執行時,會從主內存中重新加載共享變量的值。這樣,線程?t2?就有機會獲取到線程?t1?修改后的?s?的值,從而使?while (s == null)?條件不再成立,退出循環并打印出 “深圳”。

最后一個問題:如此,指令優化、緩存機制等這些為了提高效率的手段豈不是以帶來新風險為代價的?

很不幸,確實是這樣!坑也挺多的呀!

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

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

相關文章

使用docker在manjaro linux系統上運行windows和ubuntu

因為最近項目必須要使用指定版本的solidworks和maxwell(都只能在win系統上使用), 且目前的ubuntu容器是沒有桌面的,導致我運行不了一些帶圖形的ros2功能。無奈之下,決定使用docker-compose寫一下配置文件,徹底解決問題…

Elasticsearch中的_source字段講解

_source 在 Elasticsearch 查詢中用于限制返回的字段,類似于 SQL 中的 SELECT 指定列。 代碼示例: esSearchResults = es_service.search_documents({"query": {"terms": {"file_id":

【論文閱讀20】-CNN-Attention-BiGRU-滑坡預測(2025-03)

這篇論文主要探討了基于深度學習的滑坡位移預測模型,結合了MT-InSAR(多時相合成孔徑雷達干涉測量)觀測數據,提出了一種具有可解釋性的滑坡位移預測方法。 [1] Zhou C, Ye M, Xia Z, et al. An interpretable attention-based deep…

C++ 的 IO 流

💬 :如果你在閱讀過程中有任何疑問或想要進一步探討的內容,歡迎在評論區暢所欲言!我們一起學習、共同成長~! 👍 :如果你覺得這篇文章還不錯,不妨順手點個贊、加入收藏,并…

spring cloud gateway前面是否必須要有個nginx

在 **"客戶端 → Nginx (前置限流) → Spring Cloud Gateway → 微服務(Sentinel 熔斷限流)"** 的架構中,**Spring Cloud Gateway 前面并不強制要求必須有 Nginx**,是否需要取決于具體場景。以下是詳細分析: 一、必須使用 Nginx 的…

Spark和Hadoop之間的對比和聯系

(一)Spark概述 Spark是一種基于內存的快速、通用、可拓展的大數據分析計算引擎。Hadoop是一個分布式系統基礎架構。 1)官網地址:Apache Spark? - Unified Engine for large-scale data analytics 2)文檔查看地址&…

多線程進階(Java)

注:此博文為本人學習過程中的筆記 1.常見的鎖策略 當我們需要自己實現一把鎖時,需要關注鎖策略。Java提供的synchronized已經非常好用了,覆蓋了絕大多數的使用場景。此處的鎖策略并不是和Java強相關的,只要涉及到并發編程&#…

c++STL——stack、queue、priority_queue的模擬實現

文章目錄 stack、queue、priority_queue的模擬實現使用部分模擬實現容器適配器deque的介紹原理真實結構deque的迭代器deque的操作deque的優缺點 stack的模擬實現按需實例化queue的模擬實現priority_queue的模擬實現為何引入仿函數代碼實現 stack、queue、priority_queue的模擬實…

【深度學習—李宏毅教程筆記】Transformer

目錄 一、序列到序列(Seq2Seq)模型 1、Seq2Seq基本原理 2、Seq2Seq模型的應用 3、Seq2Seq模型還能做什么? 二、Encoder 三、Decoder 1、Decoder 的輸入與輸出 2、Decoder 的結構 3、Non-autoregressive Decoder 四、Encoder 和 De…

C++鐫刻數據密碼的樹之銘文:二叉搜索樹

文章目錄 1.二叉搜索樹的概念2.二叉搜索樹的實現2.1 二叉搜索樹的結構2.2 二叉搜索樹的節點尋找2.2.1 非遞歸2.2.2 遞歸 2.3 二叉搜索樹的插入2.3.1 非遞歸2.3.2 遞歸 2.4 二叉搜索樹的刪除2.4.1 非遞歸2.4.2 遞歸 2.5 二叉搜索樹的拷貝 3.二叉樹的應用希望讀者們多多三連支持小…

系統架構設計師:流水線技術相關知識點、記憶卡片、多同類型練習題、答案與解析

流水線記憶要點? ?公式 總時間 (n k - 1)Δt 吞吐率 TP n / 總時間 → 1/Δt(max) 加速比 S nk / (n k - 1) | 效率 E n / (n k - 1) 關鍵概念 周期:最長段Δt 沖突?: ?數據沖突(RAW) → 旁路/…

強制重裝及驗證onnxruntime-gpu是否正確工作

#工作記錄 我們經常會遇到明明安裝了onnxruntime-gpu或onnxruntime后,無法正常使用的情況。 一、強制重新安裝 onnxruntime-gpu 及其依賴 # 強制重新安裝 onnxruntime-gpu 及其依賴 pip install --force-reinstall --no-cache-dir onnxruntime-gpu1.18.0 --extra…

桌面我的電腦圖標不見了怎么恢復 恢復方法指南

在Windows操作系統中,“我的電腦”或在較新版本中稱為“此電腦”的圖標,是訪問硬盤驅動器、外部存儲設備和系統文件的重要入口。然而,有些用戶可能會發現桌面上缺少了這個圖標,這可能是由于誤操作、系統設置更改或是不小心刪除造成…

2025.04.20【Lollipop】| Lollipop圖繪制命令簡介

Customize markers See the different options allowing to customize the marker on top of the stem. Customize stems See the different options allowing to customize the stems. 文章目錄 Customize markersCustomize stems Lollipop圖簡介R語言中的Lollipop圖使用ggp…

docker-compose搭建kafka

1、單節點docker-compose.yml version: 3 services:zookeeper:image: zookeeper:3.8container_name: zookeeperports:- "2181:2181"volumes:- ./data/zookeeper:/dataenvironment:ZOO_MY_ID: 1ZOO_MAX_CLIENT_CNXNS: 100kafka:image: bitnami/kafka:3.7container_na…

【問題】一招解決vscode輸出和終端不一致的困擾

背景(閑話Trae) Trae是挺好,用了幾天,發現它時不時檢查文件,一檢測就轉悠半天,為此我把當前環境清空,就留一個正在調的程序,結果還照樣檢測,雖然沒影響什么,…

Git,本地上傳項目到github

一、Git的安裝和下載 https://git-scm.com/ 進入官網,選擇合適的版本下載 二、Github倉庫創建 點擊右上角New新建一個即可 三、本地項目上傳 1、進入 要上傳的項目目錄,右鍵,選擇Git Bash Here,進入終端Git 2、初始化臨時倉庫…

從零開始配置spark-local模式

1. 環境準備 操作系統:推薦使用 Linux 或 macOS,Windows 也可以,但可能會有一些額外的配置問題。 Java 環境:Spark 需要 Java 環境。確保安裝了 JDK 1.8 或更高版本。 檢查 Java 版本: bash 復制 java -version 如果…

前端~地圖(openlayers)繪制車輛運動軌跡(仿高德)

繪制軌跡路線軌跡路線描邊增加起點終點圖標繪制仿高德方向箭頭模仿車輛動態運動動畫 車輛運行軌跡 車輛軌跡經緯度坐標 const linePoints [new Point([123.676031, 43.653421]),new Point([123.824347, 43.697124]),new Point([124.197882, 43.946811]),new Point([124.104498…

分布式之CAP原則:理解分布式系統的核心設計哲學

聲明:CAP中的P原則都是需要帶著的 在分布式系統的設計與實踐中,CAP原則(又稱CAP定理)是開發者必須掌握的核心理論之一。它揭示了分布式系統在一致性(Consistency)、可用性(Availability&#x…