【精心總結】java內存模型和多線程必會知識

內存模型

(1)java內存模型到底是個啥子東西?

java內存模型是java虛擬機規范定義的一種特定模型,用以屏蔽不同硬件和操作系統的內存訪問差異,讓java在不同平臺中能達到一致的內存訪問效果,是在特定的協議下對特定的內存或高速緩存進行讀寫訪問的抽象。我來簡單的總結成一句話就是:java內存模型是java定義的對計算機內存資源(包含寄存器、高速緩存、主存等)的讀寫方法和規則。 注意上面定義是我個人的理解。
隨著我們計算機技術的不斷發展,計算機的運算能力越來越強,cpu和存儲及通信子系統的速度差距越來越大,為了避免將大量寶貴的計算資源浪費在數據庫查詢、網絡通信等IO操作上,現在多線程開發已經成了我們必需的技能。而多線程開發面臨的最大問題就是數據一致性問題,線程之間如何讀到各自的數據?線程之間如何進行交互?這些都是很重要的問題。另外編譯器在編譯程序時會自動對程序進行重排序,cpu在執行指令時也會通過指令亂序的方式來提高執行效率,高速緩存也會導致變量提交到內存的順序發生變化,同時不同處理器高速緩存中的數據互相不可見,這些都導致從一個線程看另一個線程,另一個線程的內存操作似乎在亂序執行。
為了解決這些問題,java內存模型規定了一組最小保證,這組保證規定了對變量的寫入操作在何時對其他線程可見,同時也會保證在單線程環境中程序的執行結果與在嚴格串行環境中執行的結果相同(在本線程中好像順序執行一樣)。

(2)主內存和工作內存的規定

jvm虛擬機的主要目標是定義共享變量的訪問規則,java內存模型在設計時為了保證性能在可預測性和易開發性間進行了平衡,它并沒有限制編譯器的重排序優化,也沒有限制執行引擎使用處理器的寄存器和緩存與主存進行交互,在跨線程的共享數據處理中,我們仍然需要使用合適的同步操作訪問共享數據。
在java內存模型中定義了“主內存”和“工作內存”兩個概念,我們可以將主內存類比為我們計算中的內存,將工作內存類比為我們cpu中的高速緩存和寄存器,但是實際上他們并不是等價關系。java內存模型規定:所有變量都儲存在主內存中,線程對變量的所有操作都必須在工作線程中,每個線程都有自己的工作內存,他們之間互相無法訪問,線程間的交互需要通過主內存。
在主內存和工作內存的基礎上,java內存模型定義了8個最基本的原子操作,用以處理主內存和工作內存的交互。

  1. lock:鎖定內存變量
  2. unlock:解鎖內存變量
  3. read:讀取主內存內的變量
  4. load:將read讀取的變量寫入到工作內存中
  5. use:從工作內存中讀取變量到執行引擎
  6. assign:將執行引擎的變量數據寫到工作內存中
  7. store:讀取工作內存的變量
  8. write:將工作內存中的變量寫入到主內存中
    在這里插入圖片描述
(3)volatile的語義

在日常的開發中我們經常能聽大家談論volatile關鍵字,但實際上具體是如何實現的大部分人都不清楚,實際上原理并不復雜。volatile具備兩個關鍵的特性,一個是保證變量對所有線程的可見性,另一個是禁止指令重排序(包括cpu層面的指令亂序)。volatile抽象邏輯上通過“內存柵欄”實現,其使用的“柵欄”如下所示:

每個volatile寫操作前會插入StoreStore柵欄,寫操作后會插入StoreLoad柵欄
每個volatile讀操作前會插入LoadLoad柵欄,讀操作后會插入LoadStore柵欄

在字節碼層面,volatile通過lock指令實現,在volatile變量寫操作后會有一個lock addl ¥0x0, (%esp) 的命令,這個命令會將變量數據立即刷到主內存中,并利用cpu總線嗅探機制使其他線程高速緩存內的cacheline失效(cacheline是cpu高速緩存cache的基本讀寫單位),使用時必須重新到主內存Memory讀取。同時因為需要立刻刷數據到內存中,那么volatile變量操作前的所有操作都需要完全執行完成,這樣進而也保證了volatile變量寫操作前后不會出現重排序。通常volatile變量的讀寫效率和普通變量沒有多大差別,但在volatile變量并發訪問沖突非常頻繁的情況下可能造成性能的下降,具體的例子及解決方案可以百度“偽共享”問題。

(4)杠杠的先行發生原則

java內存模型主要是通過各種操作的定義實現的,包括內存變量的讀寫操作、監視器鎖定釋放、線程關閉啟動等等。java內存模型為所有的這些操作定義了一套偏序關系,我們稱之為先行發生規則(happens-before)。線程A要看到線程B的結果,那么線程A和線程B必須滿足happens-before原則,如果不滿足就可能會出現重排序。下面是具體的規則:

  • 程序順序規則:在同一個線程內,按照代碼書寫順序,寫在前面的代碼一定先于后面的代碼執行。這種單線程代碼有序是jvm通過內存柵欄幫我們實現的。
  • 管程鎖定規則:同一個鎖,unlock一定發生在lock前
  • volatile規則:volatile的寫操作先行發生于讀操作
  • 線程啟動規則:線程start方法先行于線程內所有操作
  • 線程關閉規則:線程所有操作先行發生于線程關閉操作
  • 對象終結規則:對象構造操作先行發生于它的finalize()方法
  • 傳遞性:如果A先行發生于B,B現行發生于C,那么A先行發行于C。

注意先行發生并不代表時間上的先后! 舉兩個小🌰
(1)函數A進行set操作,函數B進行get操作,即時時間上A先執行B后執行,B也不不一定能讀到A set的值,很可能剛好函數A指令還沒執行好,線程的時間片就沒了,然后B函數獲得了cpu時間片并執行完成,這時B函數根本讀不到A設置的值。
(2)另外即使是A操作先行發生于B操作,那么A操作也不一定在時間上先于B操作執行,假如AB間沒有依賴關系,那么很可能在時間上B先執行,因為jvm只會幫我們保證最終的執行結果與嚴格順序執行的結果相同,不存在依賴關系的變量或操作間仍可能重排序優化。

線程安全

(1)線程安全的定義

在并發開發中,我們首先需要保證并發的正確性,然后在此基礎上實現高效代碼的開發。在日常開發中,我們通常會將能夠安全的被多個線程使用的對象稱為線程安全對象,但這樣說可能仍不夠嚴謹,我們可以借用《java并發編程實戰》中的定義:當多個線程訪問一個對象時,如果不用考慮線程在運行時環境的調度和交替執行,也不需要額外的同步和調用方的操作協調,直接使用這個對象都能獲得正確的結果,這個對象就是線程安全的。

(2)Java中的線程安全級別

通常在java中我們可以將java按安全性強弱分為幾個級別:不可變、絕對線程安全、相對線程安全、線程兼容、線程對立,接下來我們分別簡單的介紹下。

  • 不可變
    在java中不可變對象一定是線程安全的,線程安全是不可變對象的固有屬性之一,它們的不變條件是由構造函數創建的。我們需要注意的是java中目前沒有不可變對象的明確定義,一般情況下如果對象的所有狀態變量都是不可變的,那么對象就是不可變對像(即使對象的所有域都為final類型,對象也不一定是不可變的,因為有些域是引用類型。當final修飾的引用類型時,只能保證引用地址是不變的,實際指向的對象仍然可能發生狀態變更)。
    另外經常會看見某些同學喜歡用final修飾局部變量,其實沒啥卵用。因為class文件在設計時,對于局部變量和字段(實例變量、類變量)是區別對待的。字段在class中有access_flags屬性用來記錄字段的修飾符,例如final、static、private等。而局部變量是沒有這個屬性信息的,使不使用final修飾局部變量,在經過javac的編譯后生成的class文件是一模一樣的。
  • 絕對線程安全、相對線程安全
    絕對線程安全的實現通常需要付出非常大的代價,我們平時開發中聲明為線程安全的類也并不是絕對線程安全的,而實際上指的是相對線程安全。
  • 線程兼容
    實際上我們通常說的線程不安全對象,例如HashMap、ArrayList等,其實在java中都是被定義為線程兼容類型,指我們可以通過額外的同步操作保證線程安全。
  • 線程對立
    指無論調用是否采用同步措施,都無法并發使用,比如舊版本中的suspend()和 resume()方法。
(3)保障線程安全的一些措施
  • 阻塞同步:
    synchronized的語義
    在并發編程中,我們最常用的同步手段就是synchronized,synchronized是java提供的可以保證原子性的內置鎖,是具有排他性的可重入鎖,同一個線程可以多次使用已經獲得的synchronized鎖。
    synchronized的底層實現依賴于jvm用C++實現的管程(ObjectMonitor),管程是一種類似于信號量的程序結構,它封裝了同步操作并對進程隱蔽了同步細節。其整體實現邏輯和ReentrantLock很相似,大致的結構原理可以參見:Synchronized之管程。
    我們在使用synchronized時通常有兩種方式:1.修飾方法、2.修飾代碼塊,其實兩者差別不大,本質上都是同步代碼塊。在虛擬機層面上,當用synchronized修飾方法時,class文件中會在方法表中為相應方法增加ACC_SYNCHRONIZED訪問標志,用以標識該方法為同步方法。而當用synchronized修飾代碼塊時,會在相應代碼段字節碼的前后分別插入monitorenter和monitorexit字節碼指令,用以表示該段代碼需要同步。
    當線程執行到相應的方法或代碼段時,需要先獲取對象的鎖。如果對象沒有被鎖定或當前線程已經擁有了該鎖,則將鎖計數器的值加1然后執行代碼,相應的在退出方法或代碼段時,需將計數器的值減1。而如果獲取鎖失敗,則當前線程就需要阻塞等待,直到鎖被釋放。
    由于java線程是通過映射到內核線程實現的,線程的掛起和喚醒都需要操作系統的幫助,需要從用戶態切換到內核態,需要耗費很多cpu資源,所以synchronized相對而言是一種比較重的鎖(不過隨著不斷優化,jvm通過自適應自旋、鎖消除/鎖粗化、鎖升級等逐漸讓synchronized顯得沒那么重了),需要在合適的場景恰當的使用。
    個人經驗之談:在使用synchronized時,我們可以把synchronized修飾的方法或代碼段想象成一段不可以并發訪問的臨界區資源,這種資源必須獨占使用。而如何實現獨占訪問呢?我們可以想象每個對象都有把獨占鎖,我們需要借助某個對象的獨占鎖來訪問這種臨界區資源,而同一個鎖某個時刻只能被一個線程所獲取,其他線程都得等待鎖的釋放。用synchronized修飾的實例方法(public synchronized void method())默認使用當前對象(this)的鎖,而用synchronized修飾的靜態方法(public synchronized static void method())默認使用當前對象對應的Class對象鎖,他們分別對應于synchronized修飾代碼塊中的synchronized(object)和synchronized(Object.class)。Class對象存在于方法區中,具有全局唯一性,在一個jvm實例中一個Class對象只有一把鎖,所有使用該Class對象作為鎖的靜態方法或代碼塊,執行前都必須先獲得該Class對象鎖。而同一個Class可以有很多實例對象,每個實例對象都有一個自己的鎖,使用實例對象A鎖的線程和使用實例對象B鎖的線程間不存在競爭關系。
    除了synchronized外,DougLea也幫我們實現了ReentrantLock,它和synchronized功能和實現邏輯都基本相似,不過提供更多個性化的功能,大家有時間可以學習下。
  • 非阻塞同步:
    從概念模型角度出發,我們可以認為阻塞同步是一種悲觀的并發策略,無論是否存在并發競爭,都需要先加鎖后進行操作。而非阻塞同步是一種基于沖突檢測的樂觀并發測策略,它會先執行相關操作,在提交階段才進行沖突檢測,如果存在沖突再進行相應補償,這種并發策略大部分時候不需要將線程掛起。
    最經典的非阻塞同步就是CAS,就像它的名字compare and swap一樣,在提交數據前它會比較數據版本是否正確,以決定是否提交數據。我們java API中有大量的CAS應用,譬如AQS:它會維護一個volatile state變量和一個雙向鏈表,通過CAS操作state變量,然后根據state變量的值決定線程是掛起放入雙向鏈表中還是獲得執行權。
  • 無同步方案:
    除了上面兩種同步方案外,我們還有很多代碼是無狀態的、本身并不需要同步的,我一般稱這種代碼為純代碼。這種代碼無任何狀態,它們不依賴堆中數據和公用的系統資源,也是線程安全的。
    另外如果如果我們能將共享數據的可見范圍限制在同一個線程范圍內,那么無需同步也能保證線程間不出現數據爭用問題。我們可以通過java.lang.ThreadLocal類來實現線程本地存儲的功能。
    ThreadLocal實現介紹
    ThreadLocal的實現并不算復雜,首先每個線程Thread對象都維護了一個ThreadLocalMap,這個Map是由ThreadLocal類實現的一個使用線性探測的自定義Map,Map的key是ThreadLocal對象的引用,而value就是我們需要存儲的本地線程變量。
    如下所示當我們使用ThreadLocal時,需先new一個ThreadLocal對象threadLocalA,當使用threadLocalA保存本地線程變量(“東哥真帥!”)時,會先獲取當前Thread對象中的ThreadLocalMap,然后將對象threadLocalA的引用作為key,本地線程變量(“東哥真帥!”)作為value存進ThreadLocalMap中。
   // ThreadLocal使用方式ThreadLocal<String> threadLocalA = new ThreadLocal<>();threadLocal.set("東哥真帥!");// ThreadLocal.set源碼public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}// ThreadLocalMap.set部分源碼private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);........}}........// ThreadLocalMap類部分源碼static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}........}........

值得注意的是ThreadLocalMap并沒有使用拉鏈法,而是使用了線性探測法,并且為了提高key的離散度/減少key沖突,沒有使用對象自身的HashCode,而是使用了自定義的threadLocalHashCode。
另外還有一點非常重要:ThreadLocalMap的key被封裝成了弱引用。當ThreadLocal對象threadLocalA沒有其他強引用時,在下次GC來臨時threadLocalA就會被回收,同時ThreadLocalMap相應槽位的key值會變為null,ThreadLocalMap在每次進行get/set操作時都會主動的去清空key為null的鍵值對。ThreadLocal的這種設計主要是為了防止出現內存泄露。假如key為強引用,那么當threadLocalA使用完后,ThreadLocalMap仍持有threadLocalA的強引用,將會導致threadLocalA無法回收。
在這里插入圖片描述
順便提下java中的幾種引用類型,主要有強引用、軟引用、弱引用、虛引用。相關的知識可以參見:Java 的強引用、弱引用、軟引用、虛引用。

  • 強引用(StrongReference):強引用就是平時我們new出來的對象引用,當對象生命周期結束時才會被回收。
  • 軟引用(SoftReference):軟引用在內存空間不足時會被GC回收,內存充足時不會被回收。
  • 弱引用(WeakReference):弱引用只能活到下次GC到來。
  • 虛引用(PhantomReference):虛引用就相當于沒有引用,但虛引用會綁定一個ReferenceQueue引用隊列,當對象被回收時相關聯的虛引用就會被放入ReferenceQueue引用隊列中,可以用來釋放特定的資源。比如我們可以把jdbcConnection封裝成虛引用,同時虛引用中記錄jdbcConnection使用的堆外內存數據。當jdbcConnection被回收時,我們就可以在ReferenceQueue引用隊列通過虛引用去主動釋放堆外內存數據。
(4)鎖競爭優化方案

在并發程序中,對伸縮性的最主要威脅就是獨占方式的資源鎖。在獨占鎖上發生競爭將導致線程操作串行化和大量上線文切換,所以盡量降低和減少鎖的競爭可以提升性能以及提高程序的可伸縮性。影響鎖競爭的兩個最重要的因素是:1.鎖的請求頻率,2.鎖的持有時間。接下來介紹幾種減少鎖競爭的方案。

  1. 縮小鎖的范圍(快進快出)
    將一些與鎖無關的代碼移除同步代碼塊,尤其是時間開銷比較大的操作,可以有效的縮短鎖的持有時間,進而降低鎖競爭。
  2. 鎖分解
    如果一個鎖用來保護多個相互獨立的狀態變量,那么可以將這個鎖分解為多個鎖,每個鎖只保護一個變量,這樣就能夠降低每個鎖的請求頻率。進而提高程序的可伸縮性。
    舉個阻塞隊列的例子:大部分的阻塞隊列都會有個隊列,生產者線程池不停生產數據到隊列中,當隊列滿了就阻塞生產者線程,而生產者線程池不停消費隊列中的數據,當隊列空了就阻塞消費者線程,這時我們可以使用一個全局的ReetrantLock.Condition用于阻塞生產者線程或者消費者線程,但更好的方案是使用兩個ReetrantLock.Condition分別負責阻塞生產者線程和消費者線程,這實際上就是一種鎖分解。如下為LinkedBlockingQueue的部分代碼,其中notEmpty和notFull兩個條件鎖的使用實際上體現的就是鎖分解的思想。
    private final ReentrantLock takeLock = new ReentrantLock();private final Condition notEmpty = takeLock.newCondition();private final ReentrantLock putLock = new ReentrantLock();private final Condition notFull = putLock.newCondition();public boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException {if (e == null) throw new NullPointerException();long nanos = unit.toNanos(timeout);int c = -1;final ReentrantLock putLock = this.putLock;final AtomicInteger count = this.count;putLock.lockInterruptibly();try {while (count.get() == capacity) {if (nanos <= 0)return false;nanos = notFull.awaitNanos(nanos);}enqueue(new Node<E>(e));c = count.getAndIncrement();if (c + 1 < capacity)notFull.signal();} finally {putLock.unlock();}if (c == 0)signalNotEmpty();return true;}
  1. 鎖分段
    鎖分解是利用系統內相互獨立的狀態變量來進行鎖的拆分,但大部分系統中相互獨立的狀態變量并不多,當鎖的競爭非常激烈時,這種拆分的性能提升是有限的。在某些情況下,我們可以對系統內一組獨立對象上的鎖進行拆分,這種拆分的方式被稱為鎖分段。一個最經典的例子就是舊版的ConcurrentHashMap,在舊版ConcurrentHashMap的實現中使用了包含16個鎖的數組,每個鎖保護散列桶的16分之1,這其實體現的就是鎖分段的思想。新版ConcurrentHashMap分的就更細了,每個桶都有一個鎖,具體的細節大家有時間可以去學習下。
    舊版的ConcurrentHashMap
    大家有沒有感覺到,鎖分解有點類似于垂直分庫,而鎖分段有點像水平分表,看來優秀的設計思想都很相似。
  2. 避免使用獨占鎖
    在業務允許的情況下,我們也可以通過避免使用獨占鎖來降低鎖的競爭。例如,在讀取操作比較多的時候,我們可以用ReadWriteLock來代替ReetrantLock,這樣能提供更高的并發性和性能。對于一些訪問頻率非常高的熱點變量數據,我們可以使用原子變量來操作,也可以用volatile+CAS來代替,這些都能夠有效的提升系統性能。

在這里插入圖片描述

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

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

相關文章

工作流 activity 視頻教程 + redis 視頻教程 百度網盤分享地址

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 云盤下載都沒有密碼&#xff0c;直接下載&#xff0c;解壓有密碼&#xff1a;chongxiangmengxiangjiaoyu&#xff0c; 解壓完成后就可以…

快速解決 GRADLE 項目下載 gradle-*-all.zip 慢的問題

1、首先根據項目中 gradle\wrapper\gradle-wrapper.properties 文件的 distributionUrl 屬性的值 #Tue Feb 06 12:27:20 CET 2018 distributionBaseGRADLE_USER_HOME distributionPathwrapper/dists zipStoreBaseGRADLE_USER_HOME zipStorePathwrapper/dists distributionUrlht…

[Python] 程序結構與控制流

1. 條件語句 if、else與elif語句用于控制條件代碼的執行。條件語句的一般格式如下&#xff1a; if expression:statements elif expression:statements elif expression:statements ... else:statements 如果不需要執行任何操作&#xff0c;可以省略條件語句的else和elif子句。…

webrtc 源碼結構

apiWebRTC 接口層。包括 DataChannel, MediaStream, SDP相關的接口。各瀏覽器都是通過該接口層調用的 WebRTC。call存放的是 WebRTC “呼叫&#xff08;Call&#xff09;” 相關邏輯層的代碼。audio存放音頻網絡邏輯層相關的代碼。音頻數據邏輯上的發送&#xff0c;接收等代碼。…

mysql查詢流程解析及重要知識總結

時光荏苒啊&#xff01;在過兩個月我就工作滿三年了&#xff0c;大學畢業的情景還歷歷在目&#xff0c;而我已經默默的向油膩中年大叔進發了。作為一名苦逼的后端工程師&#xff0c;我搞過一段時間python&#xff0c;現在靠java糊口&#xff0c;但后來才發現&#xff0c;始終不…

界面無小事(八):RecyclerView增刪item

界面無小事(一): RecyclerViewCardView了解一下 界面無小事(二): 讓RecyclerView展示更多不同視圖 界面無小事(三):用RecyclerView Toolbar做個文件選擇器 界面無小事(四):來寫個滾動選擇器吧! 界面無小事(五):自定義TextView 界面無小事(六):來做個好看得側拉菜單! 界面無小事…

Failed to install Tomcat7 service 解決

見&#xff1a; http://blog.csdn.net/desow/article/details/21446197 tomcat 安裝時出現 Failed to install Tomcat7 service 今天在安裝tomcat時提示 Failed to install Tomcat7 service了&#xff0c;花了大半天的時間找到了原因&#xff0c;下面分享給大家&#xff0c;希望…

保守官僚 諾基亞就這樣迷失在智能機時代?

7月19日&#xff0c;諾基亞發布了二季度財報&#xff0c;凈虧損達到了17億美元&#xff0c;其中智能手機份額和銷售量進一步下滑&#xff0c;這個智能手機的領導者&#xff0c;正在因智能手機而急速墜落。諾記亞領先業界近十年就把握住了智能手機的趨勢&#xff0c;并推出了首款…

django集成ansibe實現自動化

動態生成主機列表和相關參數 def create_admin_domain(admin_node):workpath BASE_DIR /tools/ansible/scripthosts_file BASE_DIR /tools/ansible/host/ createhostfile()yml_file BASE_DIR /tools/ansible/yml/ create_admin_domain.ymldomain_path admin_node.doma…

extend 對象繼承

function extend(o, n, override) {for (var p in n) {if (n.hasOwnProperty(p) && (!o.hasOwnProperty(p) || override))o[p] n[p];} }// 默認參數 var options {pageIndex: 1,pageTotal: 2 };// 新設置參數 var userOptions {pageIndex: 3,pageSize: 10 }extend(o…

【spring容器啟動】之bean的實例化和初始化(文末附:spring循環依賴原理)

本次我們通過源碼介紹ApplicationContext容器初始化流程&#xff0c;主要介紹容器內bean的實例化和初始化過程。ApplicationContext是Spring推出的先進Ioc容器&#xff0c;它繼承了舊版本Ioc容器BeanFactory&#xff0c;并進一步擴展了容器的功能&#xff0c;增加了bean的自動識…

如何將自己的Java項目部署到外網

見&#xff1a;http://jingyan.baidu.com/article/90bc8fc864699af653640cf7.html 做b/s模式的web開發不同于c/s模式的客戶端開發&#xff0c;c/s模式我們只要做好生成可執行文件發送給其他人&#xff0c;其他人就可以用了。但是c/s模式不同&#xff0c;在同一局域網下&#xf…

[Swift]LeetCode916.單詞子集 | Word Subsets

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★?微信公眾號&#xff1a;山青詠芝&#xff08;shanqingyongzhi&#xff09;?博客園地址&#xff1a;山青詠芝&#xff08;https://www.cnblogs.com/strengthen/&#xff09;?GitHub地址&a…

揭秘騰訊研究院輸出策略:產品和人才的孵化器

直到現在&#xff0c;騰訊研究院創始人鄭全戰仍堅持面試招入研究院的每一個人&#xff0c;并做詳細記錄。天賦上的靈性、性格中的包容是他看重的&#xff0c;當然首先人要踏實。大約6年前&#xff0c;鄭全戰加入騰訊&#xff0c;負責籌建中國互聯網公司中的第一個研究院&#x…

java后端必會【基礎知識點】

&#xff08;一&#xff09;java集合類&#xff08;done&#xff09; 在java集合類中最常用的是Collection和Map的接口實現類。Collection又分為List和Set兩類接口&#xff0c;List的實現類有ArrayList、LinkedList、Vector、Stack&#xff0c;Set接口的實現類有HashSet、Tree…

無法連接虛擬設備ide1:0,主機上沒有相對應的設備... 解決

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 運行虛擬機出現報錯&#xff1a; 無法連接虛擬設備ide1:0&#xff0c;主機上沒有相對應的設備&#xff0c;您 要在每次開啟此虛擬機時都…

繳滿15年能領多少錢 養老金計算公式網上瘋傳

社保人員稱我省計算方式與各設區市平均工資掛鉤&#xff0c;與網上不同 最近&#xff0c;關于“延遲退休”引起各方高度關注&#xff0c;成為廣大居民十分關心的話題。是否延遲退休尚無定論&#xff0c;但在網上有不少關于養老金的計算。那網上流傳的計算方法是否科學&#xff…

48_并發編程-線程-資源共享/鎖

一、數據共享多個線程內部有自己的數據棧&#xff0c;數據不共享&#xff1b;全局變量在多個線程之間是共享的。1 # 線程數據共享不安全加鎖2 3 import time4 from threading import Thread, Lock5 6 7 num 1008 9 def func(t_lock): 10 global num 11 t_lock.acquire…

移動硬盤提示無法訪問設備硬件出現致命錯誤,導致請求失敗的資料尋回方案

J盤打不開設備硬件出現致命錯誤,導致請求失敗&#xff0c;是因為這個I盤的文件系統內部結構損壞導致的。要恢復里面的數據就必須要注意&#xff0c;這個盤不能格式化&#xff0c;否則數據會進一步損壞。具體的恢復方法看正文 工具/軟件&#xff1a;星空數據恢復軟件 步驟1&…

VMware10上新建虛擬機步驟圖解

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 第一種 : 自定義方式&#xff1a; 安裝虛擬機的過程步驟&#xff0c;基本上過程的每一步都有截圖&#xff0c;跟著過程就可以很容易的創…