本文是多線程系列之一,主要介紹多線程中比較基本的synchronized和volatile。java
很簡單,別逼無奈,天知道這羣大佬怎麼想的,用什麼思考的面試題,你面試阿里這一類編程航母也就罷了,問題是一些中型企業,在面試的時候也問的至關底層,剛開始我沒在乎,後來面試了幾家公司這一塊回答的模模糊糊,而後面完了就沒有下文了,太#@###...&&&***,你有什麼辦法?沒辦法,整唄,幸虧還有點時間git
這裏就面試中我最多見到的兩個問題給你們解答一下面試
1.1 synchronized使用編程
synchronized是java的關鍵字,用來對資源加鎖,在多線程的環境下,不可避免的要用到共享資源,此時可使用synchronized關鍵字對共享資源解鎖,防止多個線程同時對共享資源操做。安全
synchronized的使用相對簡單,如下面的代碼爲例,想要訪問synchronized修飾的代碼塊,必須先得到對象o的鎖。這裏鎖定的就是對象o。多線程
public class ThreadTest { private int count = 10; Object o = new Object(); private void m1(){ synchronized (o){ count --; System.out.println(Thread.currentThread().getId()+":count="+count); } } public static void main(String[] args){ ThreadTest tt = new ThreadTest(); new Thread(()->tt.m1(), "t1").start(); new Thread(()->tt.m1(), "t2").start(); } }
實際上並不用每次都要new一個無用的對象,咱們可使用this對象,鎖定當前對象就能夠。jvm
synchronized (this){ count --; System.out.println(Thread.currentThread().getId()+":count="+count); }
synchronized還可修飾方法,在修飾方法的時候與該方法的一開始鎖定this對象是同樣的,也就是說下面兩種寫法是同樣的。優化
private void m1(){ synchronized (this){ count --; System.out.println(Thread.currentThread().getId()+":count="+count); } } private synchronized void m1(){ count --; System.out.println(Thread.currentThread().getId()+":count="+count); }
若是是靜態方法,synchronized (this)鎖定的就是類自己。ui
synchronized的使用相對簡單,咱們在使用synchronized進行加鎖時,若是一個方法只有很小的一部分須要解鎖,那就不要給整個方法加鎖,可是若是一段代碼中有好幾個部分須要加鎖,那就不要加多個鎖,能夠在整個方法上加鎖,這樣避免了所得頻繁建立。this
1.2 synchronized原理
使用synchronized加鎖並不會剛一開始就向cpu申請鎖定資源,而是經歷了一個鎖升級的過程。假如第一個線程來訪問共享資源,剛開始的時候只是在對象的頭記錄了線程id,如今只是一個偏向鎖,若是此時又來也一個線程須要申請鎖,鎖就會升級成自旋鎖,就是線程會原地等待,經過一個while true的循環,循環十次,直到鎖釋放,若是循環十次尚未被釋放,纔會向操做系統申請資源,這種狀況就變成了重量級鎖。
synchronized是可重入的,若是m1和m2都用synchronized修飾,並且鎖定的都是同一個對象,此時若是m1調用m2,同一個線程已經獲取了這個對象的鎖,那麼m1調用m2是不須要再申請鎖的。
Volatile是java的關鍵字,用來修飾變量,它有兩個做用:
1. 禁止指令從新排序
咱們的代碼在被jvm編譯成字節碼時,jvm會對咱們的代碼的執行順序進行優化,固然這個優化保證不會改變執行的結果,在多線程的狀況下,指令重排序有可能會致使線程安全問題,並且這個問題很難發現,因此咱們用Volatile,禁止指令從新排序。
2. 使一個變量在多個線程中可見。
你們都知道java是有堆內存的,堆內存是全部線程的共享內存,可是每一個線程又有本身的私有內存空間,假若有線程A 和線程B,兩個線程都要訪問變量t,線程A 和 B都會將tcopy一份到本身的線程空間中,這樣他們對t所作的修改彼此是不知道的,由於線程並不會將t馬上寫回堆內存,一樣,線程也不會每次都從堆內存將t從新copy。若是加了Volatile修飾,若是線程對t作了修改,就會強制將t馬上寫回堆內存,一樣的,當線程使用t時,也會強制將t從堆內存從新copy。這樣就保證了線程對變量的修改對其餘線程是可見的。
可是Volatile並不能代替synchronized,若是多個線程同時修改t,只加了Volatile限制,一樣會出問題。例以下面這段代碼:
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ThreadTest1{ private volatile int count = 10000; private static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>(); public void run(){ count --; System.out.println(Thread.currentThread().getName()+":count="+count); } public static void main(String[] args){ ThreadTest1 tt1 = new ThreadTest1(); Integer limit = 10000; for(int i=0; i<limit; i++){ new Thread(tt1::run, "Thread"+i).start(); } } }
運行這段代碼就會發現,count並不會按順序減小,而是有重複出現的狀況。因此Volatile並不能代替Synchronized。
關於Synchronized和Volatile就介紹到這裏,關於多線程,其實須要瞭解的東西仍是不少的,不信?往下看
我把遇到的問題整理造成了一張知識導圖
這是我在面試的過程當中遇到的面試題,有回答上來的,也有沒回答上來的,面試完了後再網上查答案進行了相應的總結
這是我在整理面試答案的過程當中,無心發現的一份文檔,整理的很詳細,都是從源碼對於多線程進行講解,而且在每一章節的後面,都會有相應的知識圖譜進行知識點總結
相應的文章已經整理造成文檔,git掃碼獲取資料看這裏