先来看一段简单的同步锁代码
@Controller public class DemoController {
      private final Logger logger = LoggerFactory.getLogger(DemoController.class);
      @RequestMapping("/process/{orderId}")     @ResponseBody     public Map<String, Object> process(@PathVariable("orderId") String orderId) throws Exception {         synchronized (this) {             logger.debug("[{}] 开始", orderId);             Thread.sleep(1500);             logger.debug("[{}] 结束", orderId);         }
          Map<String, Object> map = new HashMap();         map.put("result", "success");         return map;     } }
   | 
 
这段代码使用了 synchronized 同步锁(锁对象是this),但是出现了一个问题,对于同一个订单的id请求时,在多线程环境下就不应该再进入这段处理代码中,
比如orderId=001时已经进入了 synchronized 中开始执行了,此时又进来一个 orderId=001的请求,因为orderId一致,就不应该再进来。
但实际情况是:因为同步锁对象是 this,而两次请求的对象不同,自然不属于同一个对象,导致 synchronized 并不会起作用。
修改上面的代码,使其同一个 orderId 不能再进入 同步锁中
@Controller public class DemoController {
      private final Logger logger = LoggerFactory.getLogger(DemoController.class);
      @RequestMapping("/process/{orderId}")     @ResponseBody     public Map<String, Object> process(@PathVariable("orderId") String orderId) throws Exception {         synchronized (orderId.intern()) {                logger.debug("[{}] 开始", orderId);             Thread.sleep(1500);             logger.debug("[{}] 结束", orderId);         }
          Map<String, Object> map = new HashMap();         map.put("result", "success");         return map;     } }
   | 
 
上述代码,将同一个 orderId 的请求变成了字符串常量池,这样,多个请求时,虽然每次都是一个新的String对象,但是这里锁的却是字符串值,因此是相等的,所以可以起到作用。
那么除了上面的字符串常量池方式,我们还可以自己维护一个缓存池对象,每次来一个orderId时从缓存池中获取对象。
@Controller public class DemoController {
      private final Logger logger = LoggerFactory.getLogger(DemoController.class);
      Map<String, Object> mutexCache = new HashMap<>();
      @RequestMapping("/process/{orderId}")     @ResponseBody     public Map<String, Object> process(@PathVariable("orderId") String orderId) throws Exception {         Object mutex4Key = mutexCache.get(orderId);         if(mutex4Key == null){             mutex4Key = new Object();                          mutexCache.put(orderId, mutex4Key);         }         synchronized (mutex4Key) {                logger.debug("[{}] 开始", orderId);             Thread.sleep(1500);             logger.debug("[{}] 结束", orderId);                          mutexCache.remove(orderId);         }
          Map<String, Object> map = new HashMap();         map.put("result", "success");         return map;     } }
   | 
 
上面写法会带来一个问题:当有大量的请求到来时,会出现前一个线程在判断 mutex4Key==null 后,然后开始new了,但还没有push进去,但此时后一个线程进来了,
它得到的判断是 mutex4Key==null,导致又开始new了。所以会有这种错误情况出现。那怎么解决呢?可以给if判断加一个同步锁:
@Controller public class DemoController {
      private final Logger logger = LoggerFactory.getLogger(DemoController.class);
      Map<String, Object> mutexCache = new HashMap<>();
      @RequestMapping("/process/{orderId}")     @ResponseBody     public Map<String, Object> process(@PathVariable("orderId") String orderId) throws Exception {         Object mute4Key = null;         synchronized (this){             mute4Key = mutexCache.get(orderId);             if (mute4Key == null){                 mute4Key = new Object();                 mutexCache.put(orderId, mute4Key);             }         }
          synchronized (mute4Key) {                logger.debug("[{}] 开始", orderId);             Thread.sleep(1500);             logger.debug("[{}] 结束", orderId);                          mutexCache.remove(orderId);         }         Map<String, Object> map = new HashMap();         map.put("result", "success");         return map;     } }
   | 
 
上面代码写的有点繁琐了,其实我们在构建缓存时可以使用 ConcurrentHashMap,ConcurrentHashMap本身就是线程安全的map的实现
     Map<String, Object> mutexCache = new ConcurrentHashMap<>();      @RequestMapping("/process/{orderId}")     @ResponseBody     public Map<String, Object> process(@PathVariable("orderId") String orderId) throws Exception {         Object mute4Key = mutexCache.computeIfAbsent(orderId, k -> new Object());
          synchronized (mute4Key) {                logger.debug("[{}] 开始", orderId);             Thread.sleep(1500);             logger.debug("[{}] 结束", orderId);                          mutexCache.remove(orderId);         }         Map<String, Object> map = new HashMap();         map.put("result", "success");         return map;     }
 
  | 
 
相关代码参考: https://github.com/hello-github-ui/java_base/blob/master/lock/src/main/java/com/example/lock/LockApplication.java