本文是多執行緒系列之一,主要介紹多執行緒中比較基本的synchronized和volatile,
起因
很簡單,別逼無奈,天知道這群大佬怎么想的,用什么思考的面試題,你面試阿里這一類編程航母也就罷了,問題是一些中型企業,在面試的時候也問的相當底層,剛開始我沒在意,后來面試了幾家公司這一塊回答的模模糊糊,然后面完了就沒有下文了,太#@###...&&&***,你有什么辦法?沒辦法,整唄,幸好還有點時間
這里就面試中我最常見到的兩個問題給大家解答一下
一、synchronized
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物件,鎖定當前物件就可以,
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)鎖定的就是類本身,
synchronized的使用相對簡單,我們在使用synchronized進行加鎖時,如果一個方法只有很小的一部分需要解鎖,那就不要給整個方法加鎖,但是如果一段代碼中有好幾個部分需要加鎖,那就不要加多個鎖,可以在整個方法上加鎖,這樣避免了所得頻繁創建,
1.2 synchronized原理
使用synchronized加鎖并不會剛一開始就向cpu申請鎖定資源,而是經歷了一個鎖升級的程序,假如第一個執行緒來訪問共享資源,剛開始的時候只是在物件的頭記錄了執行緒id,現在只是一個偏向鎖,如果此時又來也一個執行緒需要申請鎖,鎖就會升級成自旋鎖,就是執行緒會原地等待,通過一個while true的回圈,回圈十次,直到鎖釋放,如果回圈十次還沒有被釋放,才會向作業系統申請資源,這種情況就變成了重量級鎖,
synchronized是可重入的,如果m1和m2都用synchronized修飾,而且鎖定的都是同一個物件,此時如果m1呼叫m2,同一個執行緒已經獲取了這個物件的鎖,那么m1呼叫m2是不需要再申請鎖的,
二、Volatile
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掃碼獲取資料看這里
https://gitee.com/biwangsheng/personal.git
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/63371.html
標籤:Java
