示例一
在没有使用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 "交卷失败!";}}}
}