独立同步锁
要求
根据(比如)订单的颗粒度加锁,订单之间不阻塞,并且要保证内存不泄露。
分析
根据订单颗粒度加锁,那么加锁的对象应是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:
public class SynchronizedBlock {
public static void main(String[] args) {
for (int i = 1110; i < 1120; i++) {
new Thread(new SyncRunnable(i)).start();
new Thread(new SyncRunnable(i)).start();
}
}
/**
* 根据字符串锁,细颗粒度,同时常量池由jvm回收,保证内存不回泄露
*/
public static class SyncRunnable implements Runnable {
private Integer taskId;
public SyncRunnable(Integer taskId) {
this.taskId = taskId;
}
@Override
public void run() {
String intern = String.valueOf(taskId).intern();
synchronized (intern) {
executeTask();
}
}
/**
* 线程不安全的业务代码
*/
private void executeTask() {
System.out.println("start:" + taskId);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end:" + taskId);
}
}
}
竞态:访问的顺序性对运行结果有影响。
数据竞争:多个线程访问,但至少有一个线程在进行的是写操作。
顺序同步锁
要求
给定在不同线程内的三个业务块,顺序循环执行
分析
涉及不同线程的同步,常规的是使用wait、notify方法。
另一种是全局id规则如取余数比较,为了保证全局id的自增的线程安全,各个线程内需要加锁,但java内置的原子类(如AtomicInteger)也可以满足(此处无竞态条件)。