最近项目中遇到如下的场景:在执行数据迁移时,需要按照用户粒度加锁,因此考虑使用排他锁,迁移工具和业务服务属于两个服务,因此需要使用分布式锁。
我们使用缓存(Tair或者Redis)实现分布式锁,具体代码如下:
- @Service
- public class Locker {
- @Resource(name = "tairClientUtil")
- private TairClientUtil tairClientUtil;
- private ThreadLocal<Long> lockerBeanThreadLocal = new ThreadLocal<>();
- public void init(long userid) {
- lockerBeanThreadLocal.remove();
- lockerBeanThreadLocal.set(userid);
- }
- public void updateLock() {
- String lockKey = Constants.MIGRATION_PREFIX + lockerBeanThreadLocal.get();
- tairClientUtil.incr(lockKey, Constants.COUNT_EXPIRE);
- }
- public void invalidLock() {
- String lockKey = Constants.MIGRATION_PREFIX + lockerBeanThreadLocal.get();
- tairClientUtil.invalid(lockKey);
- }
- }
- 作者:杜琪
- 链接:http://www.jianshu.com/p/cadd53f063b9
因为每个线程可能携带不同的userid发起请求,因此在这里使用ThreadLocal变量存放userid,使得每个线程都有一份自己的副本。
参考官方文档:ThreadLocal的用法,这个类提供“thread-local”变量,这些变量与线程的局部变量不同,每个线程都保存一份改变量的副本,可以通过get或者set方法访问。如果开发者希望将类的某个静态变量(user ID或者transaction ID)与线程状态关联,则可以考虑使用ThreadLocal。
举个例子,下面的类为每个线程生成不同的ID,当某个线程第一次调用Thread.get()时,会为该线程赋予一个ID,并且在后续的调用中不再改变。
- import java.util.concurrent.atomic.AtomicInteger;
- public class ThreadId {
- // Atomic integer containing the next thread ID to be assigned
- private static final AtomicInteger nextId = new AtomicInteger(0);
- // Thread local variable containing each thread's ID
- private static final ThreadLocal<Integer> threadId =
- new ThreadLocal<Integer>() {
- @Override protected Integer initialValue() {
- return nextId.getAndIncrement();
- }
- };
- // Returns the current thread's unique ID, assigning it if necessary
- public static int get() {
- return threadId.get();
- }
- }
- 每个线程会“隐式”包含一份thread-local变量的副本,只要线程还处于活跃状态,就可以访问该变量;当线程停止后,如果没有其他线程持有该thread-local变量,则该变量的副本会提交给垃圾回收器。
- /**
- * {@link ThreadLocal} subclass that exposes a specified name
- * as {@link #toString()} result (allowing for introspection).
- *
- * @author Juergen Hoeller
- * @since 2.5.2
- * @see NamedInheritableThreadLocal
- */
- public class NamedThreadLocal<T> extends ThreadLocal<T> {
- private final String name;
- /**
- * Create a new NamedThreadLocal with the given name.
- * @param name a descriptive name for this ThreadLocal
- */
- public NamedThreadLocal(String name) {
- Assert.hasText(name, "Name must not be empty");
- this.name = name;
- }
- @Override
- public String toString() {
- return this.name;
- }
- }
- XmlBeanDefinitionReader.java
- private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
- new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded");
- /**
- * Load bean definitions from the specified XML file.
- * @param encodedResource the resource descriptor for the XML file,
- * allowing to specify an encoding to use for parsing the file
- * @return the number of bean definitions found
- * @throws BeanDefinitionStoreException in case of loading or parsing errors
- */
- public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
- Assert.notNull(encodedResource, "EncodedResource must not be null");
- if (logger.isInfoEnabled()) {
- logger.info("Loading XML bean definitions from " + encodedResource.getResource());
- }
- Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
- if (currentResources == null) {
- currentResources = new HashSet<EncodedResource>(4);
- this.resourcesCurrentlyBeingLoaded.set(currentResources);
- }
- if (!currentResources.add(encodedResource)) {
- throw new BeanDefinitionStoreException(
- "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
- }
- try {
- InputStream inputStream = encodedResource.getResource().getInputStream();
- try {
- InputSource inputSource = new InputSource(inputStream);
- if (encodedResource.getEncoding() != null) {
- inputSource.setEncoding(encodedResource.getEncoding());
- }
- return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
- }
- finally {
- inputStream.close();
- }
- }
- catch (IOException ex) {
- throw new BeanDefinitionStoreException(
- "IOException parsing XML document from " + encodedResource.getResource(), ex);
- }
- finally {
- currentResources.remove(encodedResource);
- if (currentResources.isEmpty()) {
- this.resourcesCurrentlyBeingLoaded.remove();
- }
- }
- }