示例一
在沒有使用synchronized鎖的情況下:
import java.util.HashMap;
import java.util.Map;public class NonSynchronizedSchoolExample {private static final Map<String, Integer> schoolCountMap = new HashMap<>(); // 存儲每個學校的交卷數量public static void main(String[] args) {// 創建三個線程,模擬不同學校的學生交卷Thread thread1 = new Thread(new SubmitPaperTask("西華師范大學"), "Thread-1");Thread thread2 = new Thread(new SubmitPaperTask("西南石油大學"), "Thread-2");Thread thread3 = new Thread(new SubmitPaperTask("西南石油大學"), "Thread-3");thread1.start();thread2.start();thread3.start();}// 創建任務類,模擬學生交卷static class SubmitPaperTask implements Runnable {private final String school;public SubmitPaperTask(String school) {this.school = universityName;}@Overridepublic void run() {// 直接訪問并修改 HashMap,未考慮線程安全Integer count = schoolCountMap.get(school);if (count == null) {count = 0; // 如果沒有該學校的記錄,默認值為0}// 模擬學生交卷System.out.println(school + " 的學生正在交卷...");try {Thread.sleep(1000); // 模擬交卷時間schoolCountMap.put(school, count + 1); // 增加該學校的交卷數量System.out.println(school + " 的學生交卷完畢! 當前交卷數量: " + (count + 1));} catch (InterruptedException e) {e.printStackTrace();}}}
}
在沒有使用synchronized的情況下,結果可能出現:
西華師范大學 的學生正在交卷...
西南石油大學 的學生正在交卷...
西華師范大學 的學生交卷完畢! 當前交卷數量: 1
西南石油大學 的學生交卷完畢! 當前交卷數量: 1
西南石油大學 的學生正在交卷...
西南石油大學 的學生交卷完畢! 當前交卷數量: 1
示例二
使用synchronized鎖的情況下:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;@RestController
public class SynchronizedSchoolController {// 存儲每個學校的交卷數量private static final Map<String, Object> lockMap = new HashMap<>(); // 存儲每個學校的鎖對象// 接收交卷請求@GetMapping("/submitPaper/{school}")public String submitPaper(@PathVariable String school) {synchronized (this) { // 鎖住該學校的鎖對象// 獲取當前學校的交卷數量,如果沒有則初始化為0Integer count = schoolCountMap.get(school);if (count == null) {count = 0; // 如果沒有該學校的記錄,默認值為0}// 模擬學生交卷try {Thread.sleep(1000); // 模擬交卷時間schoolCountMap.put(school, count + 1); // 增加該學校的交卷數量return school + " 的學生交卷完畢! 當前交卷數量: " + (count + 1);} catch (InterruptedException e) {e.printStackTrace();return "交卷失敗!";}}}
}
在這種情況下,synchronized鎖住的當前實例對象,在這種情況下,我們都每一個線程都是串行執行的。
示例三:
我現在想要改進代碼,我可以用synchronized鎖住(school)這個字符串,這樣不同學校的線程就是并行的,相同學校的就是串行執行的。
。。。
synchronized (school)
。。。
使用synchronized鎖school字符串的情況下,如果我們使用http接口的發送去請求的話,spring的底層不是發送傳遞的“西華師范大學”“西南石油大學”這樣的字符串常量,而是通過new String(“西華師范大學”)這樣的方式去傳遞string對象。這種情況下鎖的資源是三個不同的對象,沒有同一個資源的互斥,就會發送并行。
這涉及到 字符串池(String Pool)和 字符串對象的創建方式:
字符串常量(字符串池):
在 Java 中,字符串常量(例如 "西華師范大學"
)會被存儲在一個特殊的內存區域,稱為 字符串池。當你創建一個字符串常量時,JVM 會檢查池中是否已經存在相同的字符串對象,如果存在,就會返回池中的引用,否則將該字符串放入池中。
String str1 = "西華師范大學"; // 會被存儲在字符串池中
String str2 = "西華師范大學"; // str1 和 str2 引用同一個對象
通過 new String()
創建字符串對象:
通過 new String("西華師范大學")
創建的字符串對象不再從字符串池中獲取對象,而是直接在堆內存中創建一個新的 String
對象。這意味著,每次調用 new String()
都會創建一個新的對象,即使其內容與字符串池中的常量相同。
String str1 = new String("西華師范大學"); // 會在堆中創建一個新的 String 對象
String str2 = new String("西華師范大學"); // str1 和 str2 引用不同的對象
所以直接synchronized (school)還是會出現異常。
?
示例四:
為了解決示例三的問題,我們想到了直接synchronized ()字符串常量。?因為字符串常量都是存放在字符串常量池當中的,是唯一的,能夠形成資源互斥。
synchronized(school.intern())
但是字符串常量池里面的字符串是全局唯一的,可能會阻塞相同鎖資源的不同操作,所以進一步改進:
我們通過ConcurrentMap創建一個鎖對象
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;@RestController
public class SynchronizedSchoolController {// 使用 ConcurrentHashMap,確保線程安全private static final ConcurrentMap<String, Integer> schoolCountMap = new ConcurrentHashMap<>(); // 存儲每個學校的交卷數量private static final ConcurrentMap<String, Object> lockMap = new ConcurrentHashMap<>(); // 存儲每個學校的鎖對象// 接收交卷請求@GetMapping("/submitPaper/{school}")public String submitPaper(@PathVariable String school) {// 獲取每個學校的鎖對象,確保每個學校有獨立的鎖Object lock = lockMap.computeIfAbsent(school, key -> new Object());synchronized (lock) { // 鎖住該學校的鎖對象// 獲取當前學校的交卷數量,如果沒有則初始化為0Integer count = schoolCountMap.get(school);if (count == null) {count = 0; // 如果沒有該學校的記錄,默認值為0}// 模擬學生交卷try {Thread.sleep(1000); // 模擬交卷時間schoolCountMap.put(school, count + 1); // 增加該學校的交卷數量return school + " 的學生交卷完畢! 當前交卷數量: " + (count + 1);} catch (InterruptedException e) {e.printStackTrace();return "交卷失敗!";}}}
}