独立同步锁

要求
根据(比如)订单的颗粒度加锁,订单之间不阻塞,并且要保证内存不泄露。

分析
根据订单颗粒度加锁,那么加锁的对象应是order对象,将order对象放入orderMap(key为orderId,value为order对象),又因为要保证内存不泄露,那么需要及时释放orderMap中时间过久的键值对。

注意此处要维护orderMap的删除和加锁时候的if判断(key是否存在的获取以及不存在则创建对象的操作)存在竞态条件,是非线程安全的。需要通过类似活锁(while cas)或者阻塞块保证线程安全。

所以我们换一个思路。

使用String作为加锁的对象,不使用Long(orderId)是因为除了-128-127之外的数值默认都不在常量池中。但String通过String.intern()方法可以做到同样的字符串值是同一个对象,并且String的占用空间小,由JVM维护,十分方便。

以下是以String类型的orderId封装的独立同步锁demo:

  1. public class SynchronizedBlock {
  2. public static void main(String[] args) {
  3. for (int i = 1110; i < 1120; i++) {
  4. new Thread(new SyncRunnable(i)).start();
  5. new Thread(new SyncRunnable(i)).start();
  6. }
  7. }
  8. /**
  9. * 根据字符串锁,细颗粒度,同时常量池由jvm回收,保证内存不回泄露
  10. */
  11. public static class SyncRunnable implements Runnable {
  12. private Integer taskId;
  13. public SyncRunnable(Integer taskId) {
  14. this.taskId = taskId;
  15. }
  16. @Override
  17. public void run() {
  18. String intern = String.valueOf(taskId).intern();
  19. synchronized (intern) {
  20. executeTask();
  21. }
  22. }
  23. /**
  24. * 线程不安全的业务代码
  25. */
  26. private void executeTask() {
  27. System.out.println("start:" + taskId);
  28. try {
  29. Thread.sleep(1000);
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. System.out.println("end:" + taskId);
  34. }
  35. }
  36. }

竞态:访问的顺序性对运行结果有影响。
数据竞争:多个线程访问,但至少有一个线程在进行的是写操作。

顺序同步锁

要求
给定在不同线程内的三个业务块,顺序循环执行

分析
涉及不同线程的同步,常规的是使用wait、notify方法。

另一种是全局id规则如取余数比较,为了保证全局id的自增的线程安全,各个线程内需要加锁,但java内置的原子类(如AtomicInteger)也可以满足(此处无竞态条件)。