【面試必備】java寫spark好不好

并發編程三大特性

原子性

一個操作或者多次操作,要么所有的操作全部都得到執行并且不會受到任何因素的干擾而中斷,要么所有的操作都執行,要么都不執行

對于基本數據類型的訪問,讀寫都是原子性的【long和double可能例外】。

如果需要更大范圍的原子性保證,可以使用synchronized關鍵字滿足。

可見性

當一個變量對共享變量進行了修改,另外的線程都能立即看到修改后的最新值

volatile保證共享變量可見性,除此之外,synchronizedfinal都可以 實現可見性。

synchronized:對一個變量執行unclock之前,必須先把此變量同步回主內存中。

final:被final修飾的字段在構造器中一旦被初始化完成,并且構造器沒有把this的引用傳遞出去,其他線程中就能夠看見final字段的值。

有序性

即程序執行的順序按照代碼的先后順序執行【由于指令重排序的存在,Java 在編譯器以及運行期間對輸入代碼進行優化,代碼的執行順序未必就是編寫代碼時候的順序】,volatile通過禁止指令重排序保證有序性,除此之外,synchronized關鍵字也可以保證有序性,由【一個變量在同一時刻只允許一條線程對其進行lock操作】這條規則獲得。

CPU緩存模型是什么

高速緩存為何出現?

計算機在執行程序時,每條指令都是在CPU中執行的,而執行指令過程中,勢必涉及到數據的讀取和寫入。由于程序運行過程中的**臨時數據是存放在主存(物理內存)**當中的,這時就存在一個問題,由于CPU執行速度很快,而從內存讀取數據和向內存寫入數據的過程跟CPU執行指令的速度比起來要慢的多,因此如果任何時候對數據的操作都要通過和內存的交互來進行,會大大降低指令執行的速度。

為了解決CPU處理速度和內存不匹配的問題,CPU Cache出現了。

圖源:JavaGuide

緩存一致性問題

當程序在運行過程中,會將運算需要的數據從主存復制一份到CPU的高速緩存當中,那么CPU進行計算時就可以直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束之后,再將高速緩存中的數據刷新到主存當中。

在單線程中運行是沒有任何問題的,但是在多線程環境下問題就會顯現。舉個簡單的例子,如下面這段代碼:

i = i + 1;

按照上面分析,主要分為如下幾步:

  • 從主存讀取i的值,復制一份到高速緩存中。
  • CPU執行執行執行對i進行加1操作,將數據寫入高速緩存。
  • 運算結束后,將高速緩存中的數據刷新到內存中。

多線程環境下,可能出現什么現象呢?

  • 初始時,兩個線程分別讀取i的值,存入各自所在的CPU高速緩存中。
  • 線程T1進行加1操作,將i的最新值1寫入內存。
  • 此時線程T2的高速緩存中i的值還是0,進行加1操作,并將i的最新值1寫入內存。

最終的結果i = 1而不是i = 2,得出結論:如果一個變量在多個CPU中都存在緩存(一般在多線程編程時才會出現),那么就可能存在緩存不一致的問題。

如何解決緩存不一致

解決緩存不一致的問題,通常來說有如下兩種解決方案【都是在硬件層面上提供的方式】:

通過在總線加LOCK#鎖的方式

在早期的CPU當中,是通過在總線上加LOCK#鎖的形式來解決緩存不一致的問題。因為CPU和其他部件進行通信都是通過總線來進行的,如果對總線加LOCK#鎖的話,也就是說阻塞了其他CPU對其他部件訪問(如內存),從而使得只能有一個CPU能使用這個變量的內存。比如上面例子中 如果一個線程在執行 i = i +1,如果在執行這段代碼的過程中,在總線上發出了LCOK#鎖的信號,那么只有等待這段代碼完全執行完畢之后,其他CPU才能從變量i所在的內存讀取變量,然后進行相應的操作。這樣就解決了緩存不一致的問題。

但,有一個問題,在鎖住總線期間,其他CPU無法訪問內存,導致效率低下,于是就出現了下面的緩存一致性協議。

通過緩存一致性協議

較著名的就是Intel的MESI協議,MESI協議保S證了每個緩存中使用的共享變量的副本是一致的。

當CPU寫數據時,如果發現操作的變量是共享變量,即在其他CPU中也存在該變量的副本,會發出信號通知其他CPU將該變量的緩存行置為無效狀態,因此當其他CPU需要讀取這個變量時,發現自己緩存中緩存該變量的緩存行是無效的【嗅探機制:每個處理器通過嗅探在總線上傳播的數據來檢查自己的緩存的值是否過期】,那么它就會從內存重新讀取

基于MESI一致性協議,每個處理器需要不斷從主內存嗅探和CAS不斷循環,無效交互會導致總線帶寬達到峰值,出現總線風暴

JMM內存模型是什么

JMM【Java Memory Model】:Java內存模型,是java虛擬機規范中所定義的一種內存模型,Java內存模型是標準化的,屏蔽掉了底層不同計算機的區別,以實現讓Java程序在各種平臺下都能達到一致的內存訪問效果

它描述了Java程序中各種變量【線程共享變量】的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取變量這樣的底層細節。

注意,為了獲得較好的執行性能,Java內存模型并沒有限制執行引擎使用處理器的寄存器或者高速緩存來提升指令執行速度,也沒有限制編譯器對指令進行重排序。也就是說,在java內存模型中,也會存在緩存一致性問題和指令重排序的問題。

JMM的規定

所有的共享變量都存儲于主內存,這里所說的變量指的是【實例變量和類變量】,不包含局部變量,因為局部變量是線程私有的,因此不存在競爭問題

每個線程都有自己的工作內存(類似于前面的高速緩存)。線程對變量的所有操作都必須在工作內存中進行,而不能直接對主存進行操作。

每個線程不能訪問其他線程的工作內存。

Java對三大特性的保證

原子性

在Java中,對基本數據類型的變量的讀取和賦值操作是原子性操作,即這些操作是不可被中斷的,要么執行,要么不執行。

為了更好地理解上面這句話,可以看看下面這四個例子:

x = 10;  	//1
y = x;   	//2
x ++;    	//3
x = x + 1;  //4
  1. 只有語句1是原子性操作:直接將數值10賦值給x,也就是說線程執行這個語句的會直接將數值10寫入到工作內存中
  2. 語句2實際包含兩個操作:先去讀取x的值,再將x的值寫入工作內存,雖然兩步分別都是原子操作,但是合起來就不能算作原子操作了。
  3. 語句3和4表示:先讀取x的值,進行加1操作,寫入新的值

需要注意的點:

  • 在32位平臺下,對64位數據的讀取和賦值是需要通過兩個操作來完成的,不能保證其原子性。在目前64位JVM中,已經保證對64位數據的讀取和賦值也是原子性操作了。
  • Java內存模型只保證了基本讀取和賦值是原子性操作,如果要實現更大范圍操作的原子性,可以通過synchronized和Lock來實現。

可見性

Java提供了volatile關鍵字來保證可見性。

當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值。

另外,通過synchronized和Lock也能夠保證可見性,synchronized和Lock能保證同一時刻只有一個線程獲取鎖然后執行同步代碼,并且在釋放鎖之前會將對變量的修改刷新到主存當中。因此可以保證可見性。

有序性

在Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程并發執行的正確性。

在Java里面,可以通過volatile關鍵字來保證有序性,另外也可以通過synchronized和Lock來保證有序性。

Java內存模型具備一些先天的有序性,前提是兩個操作滿足happens-before原則,摘自《深入理解Java虛擬機》:

  • 程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生于書寫在后面的操作【讓程序看起來像是按照代碼順序執行,虛擬機只會對不存在數據依賴性的指令進行重排序,只能保證單線程中執行結果的正確性,多線程結果正確性卻無法保證】
  • 鎖定規則:一個unLock操作先行發生于后面對同一個鎖額lock操作
  • volatile變量規則:對一個變量的寫操作先行發生于后面對這個變量的讀操作
  • 傳遞規則:如果操作A先行發生于操作B,而操作B又先行發生于操作C,則可以得出操作A先行發生于操作C
  • 線程啟動規則:Thread對象的start()方法先行發生于此線程的每個一個動作
  • 線程中斷規則:對線程interrupt()方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生
  • 線程終結規則:線程中所有的操作都先行發生于線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行
  • 對象終結規則:一個對象的初始化完成先行發生于他的finalize()方法的開始

如果兩個操作的執行次序無法從happens-before原則推導出來,那么它們就不能保證它們的有序性,虛擬機可以隨意地對它們進行重排序。

volatile解決的問題

  • 保證了不同線程對共享變量【類的成員變量,類的靜態成員變量】進行操作是時的可見性,一個線程修改了某個變量的值,新值對其他線程來說是立即可見的

  • 禁止指令重排序。

舉個簡單的例子,看下面這段代碼:

//線程1
boolean volatile stop = false;
while(!stop){doSomething();
}
//線程2
stop = true;
  1. 線程1和2各自都擁有自己的工作內存,線程1和線程2首先都會將stop變量的值拷貝一份放到自己的工作內存中,
  2. 共享變量stop通過volatile修飾,線程2將stop的值改為true將會立即寫入主內存。
  3. 線程2寫入主內存之后,導致線程1工作內存中緩存變量stop的緩存行無效。
  4. 線程1的工作內存中緩存變量stop的緩存行無效,導致線程1會再次從主存中讀取stop值。

volatile保證原子性嗎?怎么解決?

volatile無法保證原子性,如對一個volatile修飾的變量進行自增操作i ++,無法保證多線程下結果的正確性。

解決方法:

  • 使用synchronized關鍵字或者Lock加鎖,保證某個代碼塊 在同一時刻只能被一個線程執行。
  • 使用JUC包下的原子類,如AtomicInteger等。【Atomic利用CAS來實現原子操作】。

volatile的實現原理

下面這段話摘自《深入理解Java虛擬機》:

觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的匯編代碼發現,加入volatile關鍵字時,會多出一個lock前綴指令。

lock前綴指令實際上相當于一個內存屏障(也成內存柵欄),內存屏障會提供3個功能:

  • 它確保指令重排序時不會把其后面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的后面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成;
  • 它會強制將對緩存的修改操作立即寫入主存;
  • 如果是寫操作,它會導致其他CPU中對應的緩存行無效。

volatile和synchronized的區別

volatile變量讀操作的性能消耗與普通變量幾乎沒有什么差別,但是寫操作則會慢一些,因為它需要在本地代碼中插入許多內存屏障指令來保證處理器不發生亂序執行。不過即便如此,大多數場景下volatile的總開銷仍然要比鎖來的低

  • volatile只能用于變量,而synchronized可以修飾方法以及代碼塊。
  • volatile能保證可見性,但是不能保證原子性。synchronized兩者都能保證。如果只是對一個共享變量進行多個線程的賦值,而沒有其他的操作,推薦使用volatile,它更加輕量級。
  • volatile 關鍵字主要用于解決變量在多個線程之間的可見性,而 synchronized 關鍵字解決的是多個線程之間訪問資源的同步性。

volatile的使用條件

使用volatile必須具備兩個條件【保證原子】:

  • 對變量的寫操作不依賴于當前值。
  • 該變量沒有包含在具有其他變量的不變式中。

Java面試核心知識點筆記

其中囊括了JVM、鎖、并發、Java反射、Spring原理、微服務、Zookeeper、數據庫、數據結構等大量知識點。

螞蟻金服(Java研發崗),26歲小伙斬獲三面,收獲Offer定級P6

Java中高級面試高頻考點整理

螞蟻金服(Java研發崗),26歲小伙斬獲三面,收獲Offer定級P6

更多Java進階知識筆記文檔分享,這些對于面試還是學習來說都是一份不錯的學習資料

有需要的朋友可以戳這里即可免費領取

螞蟻金服(Java研發崗),26歲小伙斬獲三面,收獲Offer定級P6

最后還分享Java進階學習及面試必備的視頻教學

螞蟻金服(Java研發崗),26歲小伙斬獲三面,收獲Offer定級P6

更多Java進階知識筆記文檔分享,這些對于面試還是學習來說都是一份不錯的學習資料

有需要的朋友可以戳這里即可免費領取

[外鏈圖片轉存中…(img-ujMXkyia-1626689158699)]

最后還分享Java進階學習及面試必備的視頻教學

[外鏈圖片轉存中…(img-EN32CykJ-1626689158701)]

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

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

相關文章

【面試必備】java面試題視頻講解

二、我們先來看看這份筆記到底有什么 1、先把kubernetes跑起來(先跑起來創建kubernetes集群部署應用訪問應用Scale應用滾動更新) 2、重要概念 3、部署kubernetes Cluster(安裝docker安裝 kubelet.kubeadm和 kubectll用kubeadm 創建cluster&a…

【面試總結】java測試工程師培訓

阿里P8級架構師核心理論落地篇 再造淘寶,貫穿全系,阿里團隊代碼落地,詳細每個版本迭代,拒絕2-3個月PPT架構師再造淘寶之咚寶-技術支撐-完整搭建DevOps再造淘寶之咚寶-統一規則-代碼規范落地解析再造淘寶之咚寶搭建基礎服務再造淘…

進程組的應用

一、實驗1 題目:利用進程扇完成一個小實驗。該進程扇有 1 個父進程和 3 個子進程,我們希望達到圖 1 中的效果,即將進程 0 (父進程)和進程 1 設置成一組,假設為組 1,將進程 2 和 進程 3 設置成另一個組,假設…

【原理+實戰+視頻+源碼】docker映射端口教程

阿里巴巴Java崗面試題分享 1.HashMap 的內部結構?內部原理?和 HashTable 的區別,假如發?了 hash 碰撞,如何設計能讓遍歷效率?? 2.講一講講講 ConcurrentHashMap吧。 3.講一下JVM虛擬機內存結構,以及它…

前臺進程組、后臺進程組

一、前臺進程組、后臺進程組 cat | cat & cat | cat | cat 輸出結果&#xff1a; 二、主要函數應用 1. tcgetpgrp函數原型&#xff1a; #include <unistd.h> pid_t tcgetpgrp(int fd);返回值&#xff1a;若成功&#xff0c;返回前臺進程組ID, 若出錯&#xff0c;…

【原理+實戰+視頻+源碼】docker權限參數

一面&#xff1a; 個人介紹加項目介紹20分鐘 微服務架構是什么&#xff0c;它的優缺點&#xff1f; ACID CAP BASE理論 分布式一致性協議&#xff0c;二段、三段、TCC&#xff0c;優缺點 RPC過程 服務注冊中心宕機了怎么辦&#xff1f; 微服務還有其他什么組件 分布式架…

【孤兒進程】孤兒進程組、守護進程

一、孤兒進程組 1. 孤兒進程的定義&#xff1a; 定義1&#xff1a;該進程組的每個成員的父進程要么是該組的成員&#xff0c;要么在其它會話中。 定義2&#xff1a;一個進程不是孤兒進程組的條件是——該組有一個進程&#xff0c;其父進程在屬于同一會話的另一個組中。 只要能…

【吐血整理】用java編寫一個登陸界面

結構化數據、非結構化數據與半結構化數據 文章的開始&#xff0c;聊一下結構化數據、非結構化數據與半結構化數據&#xff0c;因為數據特點的不同&#xff0c;將在技術上直接影響存儲引擎的選型。 首先是結構化數據&#xff0c;根據定義結構化數據指的是由二維表結構來邏輯表…

線程概念

什么是線程 LWP&#xff1a;light weight process 輕量級的進程&#xff0c;本質仍是進程(在Linux環境下&#xff09; 進程&#xff1a;獨立地址空間&#xff0c;擁有PCB 線程&#xff1a;也有PCB&#xff0c;但沒有獨立的地址空間(共享) 區別&#xff1a;在于是否有共享地址…

【大牛瘋狂教學】cdhkafka打開的文件描述符臨界閾值

基本知識 1.學會分析源碼 程序員每天都和代碼打交道。經過數年的基礎教育和職業培訓&#xff0c;大部分程序員都會「寫」代碼&#xff0c;或者至少會抄代碼和改代碼。但是&#xff0c;會讀代碼的并不在多數&#xff0c;會讀代碼又真正讀懂一些大項目的源碼的&#xff0c;少之…

線程清理函數

一、線程清理函數 #include <pthread.h> void ptread_clean_push(void (*rtn) (void *), void *arg); 注冊清理函數&#xff0c;押棧 void ptread_clean_pop(int excute); 清理函數&#xff0c;出棧 分析&#xff1a;這兩個函數是成對出現的…

【大牛瘋狂教學】mysqlinnodb和myisam

02 JVM 線程JVM內存區域JVM運行時內存垃圾回收與算法JAVA四種引用類型GC分代收集算法 VS 分區收集算法GC垃圾收集器JAVA IO/NIOJVM類加載器 03 JAVA集合 接口繼承關系和實現LISTSETMAP 04 JAVA多線程并發 JAVA并發知識庫JAVA線程實現/創建方式4種線程池線程生命周期&#xf…

【大牛瘋狂教學】深入拆解java虛擬機百度云

Java基礎核心筆記總結 由于篇幅限制&#xff0c;我就只以截圖展示目錄內容以及部分筆記內容&#xff0c;獲取完整版王者級核心寶典只需要點擊點贊關注即可獲取領取方式&#xff01; 在這個部分我們總結了Java的基礎知識&#xff0c;涵蓋了&#xff1a;概述、開發環境、開發環境…

不可靠的信號、可靠信號

一、不可靠信號 lnux信號機制基本上是從UNIX系統中繼承過來的。早期UNIX系統中的信號機制比較簡單和原始&#xff0c;后來在實踐中暴露一些問題&#xff0c;它的主要問題是&#xff1a; 1. 進程每次處理信號后&#xff0c;就將對信號的響應設置為默認動作&#xff0c;在某些情…

【大牛系列教學】docker.service配置文件詳情

01 阿里中間件&#xff08;四面&#xff0c;Java崗&#xff09; 1.1 Java中間件一面 技術一面考察范圍 重點問了Java線程鎖&#xff1a;synchronized 和ReentrantLock相關的底層實現 線程池的底層實現以及常見的參數 數據結構基本都問了一遍&#xff1a;鏈表、隊列等 Java內存…

可重入函數和不可重入函數

一、可重入函數 為了增強程序的穩定性&#xff0c;在信號處理函數中應使用可重入函數。 所謂可重入函數是指一個可以被多個任何調用的過程&#xff0c;任務在調用時不必擔心數據是否出錯。因為進程在收到信號后&#xff0c;就將跳轉到信號處理函數去接著執行&#xff0c;如果…

【大牛系列教學】java面試常考的編程題

1. 面試官&#xff1a;工作中使用過Zookeeper嘛&#xff1f;你知道它是什么&#xff0c;有什么用途呢&#xff1f; 小菜雞的我&#xff1a; 有使用過的&#xff0c;使用ZooKeeper作為dubbo的注冊中心&#xff0c;使用ZooKeeper實現分布式鎖。ZooKeeper&#xff0c;它是一個開…

中斷的系統調用

系統調用可分為兩類&#xff1a;慢速系統調用和其他系統調用 慢速系統調用&#xff1a;可能會使進程永遠阻塞的一類&#xff0c;如果在阻塞期間收到一個信號&#xff0c;該系統調用就被中斷&#xff0c;不再繼續執行(早期)&#xff1b;也可以設定系統調用是否重啟。如&#xf…

【好文推薦】java模板引擎性能

01. 基礎架構&#xff1a;一條SQL查詢語句是如何執行的&#xff1f; MySQL 的邏輯架構圖 02. 日志系統&#xff1a;一條SQL更新語句是如何執行的&#xff1f; 之前你可能經常聽DBA同事說&#xff0c;MySQL可以恢復到半個月內任意一秒的狀態&#xff0c;驚嘆的同時&#xff0c;…

函數sigqueue

一、函數sigqueue sigqueue函數原型&#xff1a; 函數作用&#xff1a;新的發送信號系統調用&#xff0c;主要是針對實時信號提出的支持信號帶有參數&#xff0c;與函數sigaction&#xff08;&#xff09;配合使用 int sigqueue(pid_t pid, int signo, const union sigval v…