Java并发编程学习之ThreadLocal源码详析

前言

多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的,多线程访问同一个共享变量特别容易出现并发问题,特别是多个线程需要对一个共享变量进行写入时候,为了保证线程安全,

一般需要使用者在访问共享变量的时候进行适当的同步,如下图所示:

可以看到同步的措施一般是加锁,这就需要使用者对锁也要有一定了解,这显然加重了使用者的负担。那么有没有一种方式当创建一个变量的时候,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实ThreaLocal就可以做这个事情,注意一下,ThreadLocal的出现并不是为了解决上面的问题而出现的。

ThreadLocal是在JDK包里面提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而避免了线程安全问题,创建一个ThreadLocal变量后,

每个线程会拷贝一个变量到自己的本地内存,如下图:

好了,现在我们思考一个问题:ThreadLocal的实现原理,ThreadLocal作为变量的线程隔离方式,其内部又是如何实现的呢?

首先我们要看ThreadLocal的类图结构,如下图所示:

 如

上类图可见,Thread类中有一个threadLocals和inheritableThreadLocals 都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的Hashmap,默认每个线程中这两个变量都为null,只有当线程第一次调用了ThreadLocal的set或者get方法的时候才会创建。

其实每个线程的本地变量不是存到ThreadLocal实例里面的,而是存放到调用线程的threadLocals变量里面。也就是说ThreadLocal类型的本地变量是存放到具体线程内存空间的。

ThreadLocal其实就是一个外壳,它通过set方法把value值放入调用线程threadLocals里面存放起来,当调用线程调用它的get方法的时候再从当前线程的threadLocals变量里面拿出来使用。如果调用线程如果一直不终止的话,那么这个本地变量会一直存放到调用线程的threadLocals变量里面,

因此,当不需要使用本地变量时候可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals变量里面删除该本地变量。可能还有人会问threadLocals为什么设计为Map结构呢?很明显是因为每个线程里面可以关联多个ThreadLocal变量。

接下来我们可以进入到ThreadLocal中的源码如看看,如下代码所示:

主要看set,get,remove这三个方法的实现逻辑,如下:

先看set(T var1)方法

public void set(T var1) {     //(1)获取当前线程
 Thread var2 = Thread.currentThread();     //(2) 当前线程作为key,去查找对应的线程变量,找到则设置
 ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
 if(var3 != null) {
 var3.set(this, var1);
 } else {       //(3) 第一次调用则创建当前线程对应的Hashmap
 this.createMap(var2, var1);
 }
 }

如上代码(1)首先获取调用线程,然后使用当前线程作为参数调用了 getMap(var2) 方法,getMap(Thread var2) 代码如下:

   ThreadLocal.ThreadLocalMap getMap(Thread var1) {
 return var1.threadLocals;
 }

可知getMap(var2) 所作的就是获取线程自己的变量threadLocals,threadlocal变量是绑定到了线程的成员变量里面。

如果getMap(var2) 返回不为空,则把 value 值设置进入到 threadLocals,也就是把当前变量值放入了当前线程的内存变量 threadLocals,threadLocals 是个 HashMap 结构,其中 key 就是当前 ThreadLocal 的实例对象引用,value 是通过 set 方法传递的值。

如果 getMap(var2) 返回空那说明是第一次调用 set 方法,则创建当前线程的 threadLocals 变量,下面看 createMap(var2, var1) 里面做了啥呢?

 void createMap(Thread var1, T var2) {
 var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2);
 }

可以看到的就是创建当前线程的threadLocals变量。

接下来我们再看get()方法,代码如下:

public T get() {    //(4)获取当前线程
 Thread var1 = Thread.currentThread();    //(5)获取当前线程的threadLocals变量
 ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);    //(6)如果threadLocals不为null,则返回对应本地变量值
 if(var2 != null) {
 ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
 if(var3 != null) {
 Object var4 = var3.value;
 return var4;
 }
 }
    //(7)threadLocals为空则初始化当前线程的threadLocals成员变量。
 return this.setInitialValue();
 }

代码(4)首先获取当前线程实例,如果当前线程的threadLocals变量不为null则直接返回当前线程的本地变量。否则执行代码(7)进行初始化,setInitialValue()的代码如下:

private T setInitialValue() {    //(8)初始化为null
 Object var1 = this.initialValue();
 Thread var2 = Thread.currentThread();
 ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);    //(9)如果当前线程变量的threadLocals变量不为空
 if(var3 != null) {
 var3.set(this, var1);    //(10)如果当前线程的threadLocals变量为空
 } else {
 this.createMap(var2, var1);
 }

 return var1;
 }

如上代码如果当前线程的 threadLocals 变量不为空,则设置当前线程的本地变量值为 null,否者调用 createMap 创建当前线程的 createMap 变量。

接着我们在看看void remove()方法,代码如下:

public void remove() {
 ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread());
 if(var1 != null) {
 var1.remove(this);
 }
 }

如上代码,如果当前线程的 threadLocals 变量不为空,则删除当前线程中指定 ThreadLocal 实例的本地变量。

接下来我们看看具体演示demo,代码如下:

/**
 * Created by cong on 2018/6/3.
 */
public class ThreadLocalTest {
 //(1)打印函数
 static void print(String str) {
 //1.1 打印当前线程本地内存中localVariable变量的值
 System.out.println(str + ":" + localVariable.get());
 //1.2 清除当前线程本地内存中localVariable变量
 //localVariable.remove();
 }

 //(2) 创建ThreadLocal变量
 static ThreadLocal<String> localVariable = new ThreadLocal<>();
 public static void main(String[] args) {

 //(3) 创建线程one
 Thread threadOne = new Thread(new Runnable() {
 public void run() {
 //3.1 设置线程one中本地变量localVariable的值
 localVariable.set("线程1的本地变量");
 //3.2 调用打印函数
 print("线程1----->");
 //3.3打印本地变量值
 System.out.println("移除线程1本地变量后的结果" + ":" + localVariable.get());

 }
 });
 //(4) 创建线程two
 Thread threadTwo = new Thread(new Runnable() {
 public void run() {
 //4.1 设置线程one中本地变量localVariable的值
 localVariable.set("线程2的本地变量");
 //4.2 调用打印函数
 print("线程2----->");
 //4.3打印本地变量值
 System.out.println("移除线程2本地变量后的结果" + ":" + localVariable.get());

 }
 });
 //(5)启动线程
 threadOne.start();
 threadTwo.start();
 }
}

代码(2)创建了一个 ThreadLocal 变量;

代码(3)、(4)分别创建了线程 1和 2;

代码(5)启动了两个线程;

线程 1 中代码 3.1 通过 set 方法设置了 localVariable 的值,这个设置的其实是线程 1 本地内存中的一个拷贝,这个拷贝线程 2 是访问不了的。然后代码 3.2 调用了 print 函数,代码 1.1 通过 get 函数获取了当前线程(线程 1)本地内存中 localVariable 的值;

线程 2 执行类似线程 1。

运行结果如下:

这里要注意一下ThreadLocal的内存泄漏问题

  每个线程内部都有一个名字为 threadLocals 的成员变量,该变量类型为 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this 引用,value 则为我们 set 时候的值,每个线程的本地变量是存到线程自己的内存变量 threadLocals 里面的,如果当前线程一直不消失那么这些本地变量会一直存到,

所以可能会造成内存泄露,所以使用完毕后要记得调用 ThreadLocal 的 remove 方法删除对应线程的 threadLocals 中的本地变量。

解开代码1.2的注释后,再次运行,运行结果如下:

我们有没有想过这样的一个问题:子线程中是否获取到父线程中设置的 ThreadLocal 变量的值呢?

  这里可以告诉大家,在子线程中是获取不到父线程中设置的 ThreadLocal 变量的值的。那么有办法让子线程访问到父线程中的值吗?为了解决该问题 InheritableThreadLocal 应运而生,InheritableThreadLocal 继承自 ThreadLocal,提供了一个特性,就是子线程可以访问到父线程中设置的本地变量。

首先我们先进入InheritableThreadLocal这个类的源码去看,如下:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
 public InheritableThreadLocal() {
 }
  //(1)
 protected T childValue(T var1) {
 return var1;
 }
  //(2)
 ThreadLocalMap getMap(Thread var1) {
 return var1.inheritableThreadLocals;
 }
  //(3)
 void createMap(Thread var1, T var2) {
 var1.inheritableThreadLocals = new ThreadLocalMap(this, var2);
 }
}

可以看到InheritableThreadlocal继承ThreadLocal,并重写了三个方法,在上面的代码已经标出了。代码(3)可知InheritableThreadLocal重写createMap方法,那么可以知道现在当第一次调用set方法时候创建的是当前线程的inhertableThreadLocals变量的实例,而不再是threadLocals。

代码(2)可以知道当调用get方法获取当前线程的内部map变量时候,获取的是inheritableThreadLocals,而不再是threadLocals。

关键地方来了,重写的代码(1)是何时被执行的,以及如何实现子线程可以访问父线程本地变量的。这个要从Thread创建的代码看起,Thread的默认构造函数以及Thread.java类的构造函数如下:

/**
 * Created by cong on 2018/6/3.
 */
  public Thread(Runnable target) {
  init(null, target, "Thread-" + nextThreadNum(), 0);
  }
  private void init(ThreadGroup g, Runnable target, String name,
  long stackSize, AccessControlContext acc) {
  //...
  //(4)获取当前线程
  Thread parent = currentThread();
  //...
  //(5)如果父线程的inheritableThreadLocals变量不为null
  if (parent.inheritableThreadLocals != null)
  //(6)设置子线程中的inheritableThreadLocals变量
  this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  this.stackSize = stackSize;
  tid = nextThreadID();
  }

创建线程时候在构造函数里面会调用init方法,前面讲到了inheritableThreadLocal类get,set方法操作的是变量inheritableThreadLocals,所以这里inheritableThreadLocal变量就不为null,所以会执行代码(6),下面看createInheritedMap方法源码,如下:

  static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
 return new ThreadLocalMap(parentMap);
 }

可以看到createInheritedMap内部使用父线程的inheritableThreadLocals变量作为构造函数创建了一个新的ThreadLocalMap变量,然后赋值给了子线程的inheritableThreadLocals变量,那么接着进入到ThreadLocalMap的构造函数里面做了什么,源码如下:

private ThreadLocalMap(ThreadLocalMap parentMap) {
  Entry[] parentTable = parentMap.table;
  int len = parentTable.length;
  setThreshold(len);
  table = new Entry[len];

  for (int j = 0; j < len; j++) {
  Entry e = parentTable[j];
  if (e != null) {
   @SuppressWarnings("unchecked")
   ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
   if (key != null) {
   //(7)调用重写的方法
   Object value = key.childValue(e.value);//返回e.value
   Entry c = new Entry(key, value);
   int h = key.threadLocalHashCode & (len - 1);
   while (table[h] != null)
    h = nextIndex(h, len);
   table[h] = c;
   size++;
   }
  }
  }
 }

如上代码所做的事情就是把父线程的inhertableThreadLocals成员变量的值复制到新的ThreadLocalMap对象,其中代码(7)InheritableThreadLocal类重写的代码(1)也映入眼帘了。

总的来说:InheritableThreadLocal类通过重写代码(2)和(3)让本地变量保存到了具体线程的inheritableThreadLocals变量里面,线程通过InheritableThreadLocal类实例的set 或者 get方法设置变量时候就会创建当前线程的inheritableThreadLocals变量。当父线程创建子线程时候,

构造函数里面就会把父线程中inheritableThreadLocals变量里面的本地变量拷贝一份复制到子线程的inheritableThreadLocals变量里面。

好了原理了解到位了,接下来进行一个例子来验证上面所了解的东西,如下:

package com.hjc;
/**
 * Created by cong on 2018/6/3.
 */
public class InheritableThreadLocalTest {
 //(1) 创建线程变量
 public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
 public static void main(String[] args) {
 //(2) 设置线程变量
 threadLocal.set("hello Java");
 //(3) 启动子线程
 Thread thread = new Thread(new Runnable() {
  public void run() {
  //(4)子线程输出线程变量的值
  System.out.println("子线程:" + threadLocal.get());

  }
 });
 thread.start();
 //(5)主线程输出线程变量值
 System.out.println("父线程:" + threadLocal.get());
 }
}

运行结果如下:

也就是说同一个 ThreadLocal 变量在父线程中设置值后,在子线程中是获取不到的。根据上节的介绍,这个应该是正常现象,因为子线程调用 get 方法时候当前线程为子线程,而调用 set 方法设置线程变量是 main 线程,两者是不同的线程,自然子线程访问时候返回 null。

那么有办法让子线程访问到父线程中的值吗?答案是有,就用我们上面原理分析的InheritableThreadLocal。

将上面例子的代码(1)修改为:

 //(1) 创建线程变量
 public static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();

运行结果如下:

可知现在可以从子线程中正常的获取到线程变量值了。那么什么情况下需要子线程可以获取到父线程的 threadlocal 变量呢?

  情况还是蛮多的,比如存放用户登录信息的 threadlocal 变量,很有可能子线程中也需要使用用户登录信息,再比如一些中间件需要用统一的追踪 ID 把整个调用链路记录下来的情景。

Spring Request Scope 作用域 Bean 中 ThreadLocal 的使用

  我们知道 Spring 中在 XML 里面配置 Bean 的时候可以指定 scope 属性来配置该 Bean 的作用域为 singleton、prototype、request、session 等,其中作用域为 request 的实现原理就是使用 ThreadLocal 实现的。如果你想让你 Spring 容器里的某个 Bean 拥有 Web 的某种作用域,

则除了需要 Bean 级上配置相应的 scope 属性,还必须在 web.xml 里面配置如下:

<listener>
 <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

这里主要看RequestContextListener的两个方法:

public void requestInitialized(ServletRequestEvent requestEvent)

public void requestDestroyed(ServletRequestEvent requestEvent)

当一个web请求过来时候会执行requestInitialized方法:

public void requestInitialized(ServletRequestEvent requestEvent) {
    .......省略
  HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
  ServletRequestAttributes attributes = new ServletRequestAttributes(request);
  request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
  LocaleContextHolder.setLocale(request.getLocale());
  //设置属性到threadlocal变量
  RequestContextHolder.setRequestAttributes(attributes);
 }
 public static void setRequestAttributes(RequestAttributes attributes) {

  setRequestAttributes(attributes, false);
 }
 public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {
  if (attributes == null) {
   resetRequestAttributes();
  }
  else {
   //默认inheritable=false
   if (inheritable) {
    inheritableRequestAttributesHolder.set(attributes);
    requestAttributesHolder.remove();
   }
   else {
    requestAttributesHolder.set(attributes);
    inheritableRequestAttributesHolder.remove();
   }
  }
 }

可以看到上面源码,由于默认inheritable 为FALSE,我们的属性值都放到了requestAttributesHoder里面,而它的定义是:

   private static final ThreadLocal<RequestAttributes> requestAttributesHolder =new NamedThreadLocal<RequestAttributes>("Request attributes");

  private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =new NamedInheritableThreadLocal<RequestAttributes>("Request context");

其中NamedThreadLocal<T> extends ThreadLocal<T>,所以不具有继承性。

其中 NamedThreadLocal<T> extends ThreadLocal<T>,所以不具有继承性。

NameInheritableThreadLocal<T> extends InheritableThreadLocal<T>,所以具有继承性,所以默认放入到RequestContextHolder里面的属性值在子线程中获取不到。

当请求结束时候调用requestDestroyed方法,源码如下:

public void requestDestroyed(ServletRequestEvent requestEvent) {
  ServletRequestAttributes attributes =
    (ServletRequestAttributes) requestEvent.getServletRequest().getAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE);
  ServletRequestAttributes threadAttributes =
    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  if (threadAttributes != null) {
   // 我们很有可能在最初的请求线程中
   if (attributes == null) {
    attributes = threadAttributes;
   }
   //请求结束则清除当前线程的线程变量。
   LocaleContextHolder.resetLocaleContext();
   RequestContextHolder.resetRequestAttributes();
  }
  if (attributes != null) {
   attributes.requestCompleted();
  }
 }

接下来从时序图看一下 Web请求调用逻辑如何:

也就是说每次发起一个Web请求在Tomcat中context(具体应用)处理前,host匹配后会设置下RequestContextHolder属性,让requestAttributesHolder不为空,在请求结束时会清除。

因此,默认情况下放入RequestContextHolder里面的属性子线程访问不到,Spring 的request作用域的bean是使用threadlocal实现的。

接下来进行一个例子模拟请求,代码如下:

web.xml配置如下:

因为是 request 作用域,所以必须是 Web 项目,并且需要配置 RequestContextListener 到 web.xml。

<listener>
  <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

接着注入一个 request 作用域 bean 到 IOC 容器。代码如下:

<bean id="requestBean" class="hjc.test.RequestBean"
  scope="request">
  <property name="name" value="hjc" />
  <aop:scoped-proxy />
 </bean>

测试代码如下:

@WebResource("/testService")
public class TestRpc {
 @Autowired
 private RequestBean requestInfo;
 @ResourceMapping("test")
 public ActionResult test(ErrorContext context) {
  ActionResult result = new ActionResult();
  pvgInfo.setName("hjc");
  String name = requestInfo.getName();
  result.setValue(name);
  return result;
 }
}

如上首先配置 RequestContextListener 到 web.xml 里面,然后注入了 Request 作用域的 RequestBean 的实例到 IOC 容器,最后 TestRpc 内注入了 RequestBean 的实例,方法 test 首先调用了 requestInfo 的方法 setName 设置 name 属性,然后获取 name 属性并返回。

这里如果 requestInfo 对象是单例的,那么多个线程同时调用 test 方法后,每个线程都是设置-获取的操作,这个操作不是原子性的,会导致线程安全问题。而这里声明的作用域为 request 级别,也是每个线程都有一个 requestInfo 的本地变量。

上面例子方法请求的时序图如下:

我们要着重关注调用test时候发生了什么:

其实前面创建的 requestInfo 是被经过 CGliB 代理后的(感兴趣的可以研究下 ScopedProxyFactoryBean 这类),所以这里调用 setName 或者 getName 时候会被 DynamicAdvisedInterceptor 拦截的,拦击器里面最终会调用到 RequestScope 的 get 方法获取当前线程持有的本地变量。

关键来了,我们要看一下RequestScope的get方法的源码如下:

public Object get(String name, ObjectFactory objectFactory) {

  RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();//(1)
  Object scopedObject = attributes.getAttribute(name, getScope());
  if (scopedObject == null) {
   scopedObject = objectFactory.getObject();//(2)
   attributes.setAttribute(name, scopedObject, getScope());//(3)
  }
  return scopedObject;
 }

可知当发起一个请求时候,首先会通过 RequestContextListener.requestInitialized 里面调用 RequestContextHolder.setRequestAttributess 设置 requestAttributesHolder。

然后请求被路由到 TestRpc 的 test 方法后,test 方法内第一次调用 setName 方法时候,最终会调用 RequestScope.get()方法,get 方法内代码(1)获取通过 RequestContextListener.requestInitialized 设置的线程本地变量 requestAttributesHolder 保存的属性集的值。

接着看该属性集里面是否有名字为 requestInfo 的属性,由于是第一次调用,所以不存在,所以会执行代码(2)让 Spring 创建一个 RequestInfo 对象,然后设置到属性集 attributes,也就是保存到了当前请求线程的本地内存里面了。然后返回创建的对象,调用创建对象的 setName。

最后test 方法内紧接着调用了 getName 方法,最终会调用 RequestScope.get() 方法,get 方法内代码(1)获取通过 RequestContextListener.requestInitialized 设置的线程本地变量 RequestAttributes,然后看该属性集里面是否有名字为 requestInfo 的属性,

由于是第一次调用 setName 时候已经设置名字为 requestInfo 的 bean 到 ThreadLocal 变量里面了,并且调用 setName 和 getName 的是同一个线程,所以这里直接返回了调用 setName 时候创建的 RequestInfo 对象,然后调用它的 getName 方法。

到目前为止我们了解ThreadLocal 的实现原理,并指出 ThreadLocal 不支持继承性;然后紧接着讲解了 InheritableThreadLocal 是如何补偿了 ThreadLocal 不支持继承的特性;最后简单的介绍了 Spring 框架中如何使用 ThreadLocal 实现了 Reqeust Scope 的 Bean。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • 深入学习java ThreadLocal的源码知识

    简介 ThreadLocal是每个线程自己维护的一个存储对象的数据结构,线程间互不影响实现线程封闭.一般我们通过ThreadLocal对象的get/set方法存取对象. 源码分析 ThreadLocal的set方法源码如下 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 根据当前线程获得ThreadLocalMap对象 if (map != null)

  • java ThreadLocal使用案例详解

    本文借由并发环境下使用线程不安全的SimpleDateFormat优化案例,帮助大家理解ThreadLocal. 最近整理公司项目,发现不少写的比较糟糕的地方,比如下面这个: public class DateUtil { private final static SimpleDateFormat sdfyhm = new SimpleDateFormat( "yyyyMMdd"); public synchronized static Date parseymdhms(String

  • Java ThreadLocal的设计理念与作用

    Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量.因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量. 如何创建ThreadLocal变量 以下代码展示了如何创建一个ThreadLocal变量: private ThreadLocal myThreadLocal = new ThreadLocal(); 我们可以看到,通过这段代码实例化了一个ThreadLocal对象.我们只需要实例

  • Java源码解析ThreadLocal及使用场景

    ThreadLocal是在多线程环境下经常使用的一个类. 这个类并不是为了解决多线程间共享变量的问题.举个例子,在一个电商系统中,用一个Long型变量表示某个商品的库存量,多个线程需要访问库存量进行销售,并减去销售数量,以更新库存量.在这个场景中,是不能使用ThreadLocal类的. ThreadLocal适用的场景是,多个线程都需要使用一个变量,但这个变量的值不需要在各个线程间共享,各个线程都只使用自己的这个变量的值.这样的场景下,可以使用ThreadLocal.此外,我们使用ThreadL

  • 浅谈Java引用和Threadlocal的那些事

    1 背景 某一天在某一个群里面的某个群友突然提出了一个问题:"threadlocal的key是虚引用,那么在threadlocal.get()的时候,发生GC之后,key是否是null?"屏幕前的你可以好好的想想这个问题,在这里我先卖个关子,先讲讲Java中引用和ThreadLocal的那些事. 2 Java中的引用 对于很多Java初学者来说,会把引用和对象给搞混淆.下面有一段代码, User zhangsan = new User("zhangsan", 24)

  • 快速了解Java中ThreadLocal类

    最近看Android FrameWork层代码,看到了ThreadLocal这个类,有点儿陌生,就翻了各种相关博客一一拜读:自己随后又研究了一遍源码,发现自己的理解较之前阅读的博文有不同之处,所以决定自己写篇文章说说自己的理解,希望可以起到以下作用: - 可以疏通研究结果,加深自己的理解: - 可以起到抛砖引玉的作用,帮助感兴趣的同学疏通思路: - 分享学习经历,同大家一起交流和学习. 一. ThreadLocal 是什么 ThreadLocal 是Java类库的基础类,在包java.lang下

  • Java并发编程学习之ThreadLocal源码详析

    前言 多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的,多线程访问同一个共享变量特别容易出现并发问题,特别是多个线程需要对一个共享变量进行写入时候,为了保证线程安全, 一般需要使用者在访问共享变量的时候进行适当的同步,如下图所示: 可以看到同步的措施一般是加锁,这就需要使用者对锁也要有一定了解,这显然加重了使用者的负担.那么有没有一种方式当创建一个变量的时候,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实ThreaLocal就可

  • 关于Redis网络模型的源码详析

    前言 Redis的网络模型是基于I/O多路复用程序来实现的.源码中包含四种多路复用函数库epoll.select.evport.kqueue.在程序编译时会根据系统自动选择这四种库其中之一.下面以epoll为例,来分析Redis的I/O模块的源码. epoll系统调用方法 Redis网络事件处理模块的代码都是围绕epoll那三个系统方法来写的.先把这三个方法弄清楚,后面就不难了. epfd = epoll_create(1024); 创建epoll实例 参数:表示该 epoll 实例最多可监听的

  • YOLOv5中SPP/SPPF结构源码详析(内含注释分析)

    目录 一.SPP的应用的背景 二.SPP结构分析 三.SPPF结构分析 四.YOLOv5中SPP/SPPF结构源码解析(内含注释分析) 总结 一.SPP的应用的背景 在卷积神经网络中我们经常看到固定输入的设计,但是如果我们输入的不能是固定尺寸的该怎么办呢? 通常来说,我们有以下几种方法: (1)对输入进行resize操作,让他们统统变成你设计的层的输入规格那样.但是这样过于暴力直接,可能会丢失很多信息或者多出很多不该有的信息(图片变形等),影响最终的结果. (2)替换网络中的全连接层,对最后的卷

  • Java 并发编程学习笔记之核心理论基础

    并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密,这样才能写出高效.安全.可靠的多线程并发程序.本系列会从线程间协调的方式(wait.notify.notifyAll).Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制.在此基础上,我们会进一步分析java.util.concurrent包的工具类,包括其使用方式.实现源码及其背后的原理.本

  • Java 并发编程学习笔记之Synchronized简介

    一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题.从语法上讲,Synchronized总共有三种用法: (1)修饰普通方法 (2)修饰静态方法 (3)修饰代码块 接下来我就通过几个例子程序来说明一下这三种使用方式(为了便于比较,三段代码除了Synchronized的使用方式不同以外,

  • Java并发编程学习之Unsafe类与LockSupport类源码详析

    一.Unsafe类的源码分析 JDK的rt.jar包中的Unsafe类提供了硬件级别的原子操作,Unsafe里面的方法都是native方法,通过使用JNI的方式来访问本地C++实现库. rt.jar 中 Unsafe 类主要函数讲解, Unsafe 类提供了硬件级别的原子操作,可以安全的直接操作内存变量,其在 JUC 源码中被广泛的使用,了解其原理为研究 JUC 源码奠定了基础. 首先我们先了解Unsafe类中主要方法的使用,如下: 1.long objectFieldOffset(Field

  • Java并发编程之显式锁机制详解

    我们之前介绍过synchronized关键字实现程序的原子性操作,它的内部也是一种加锁和解锁机制,是一种声明式的编程方式,我们只需要对方法或者代码块进行声明,Java内部帮我们在调用方法之前和结束时加锁和解锁.而我们本篇将要介绍的显式锁是一种手动式的实现方式,程序员控制锁的具体实现,虽然现在越来越趋向于使用synchronized直接实现原子操作,但是了解了Lock接口的具体实现机制将有助于我们对synchronized的使用.本文主要涉及以下一些内容: 接口Lock的基本组成成员 可重入锁Re

  • Java基于JDK 1.8的LinkedList源码详析

    前言 上周末我们一起分析了ArrayList的源码并进行了一些总结,因为最近在看Collection这一块的东西,下面的图也是大致的总结了Collection里面重要的接口和类,如果没有意外的话后面基本上每一个都会和大家一起学习学习,所以今天也就和大家一起来看看LinkedList吧! 2,记得首次接触LinkedList还是在大学Java的时候,当时说起LinkedList的特性和应用场景:LinkedList基于双向链表适用于增删频繁且查询不频繁的场景,线程不安全的且适用于单线程(这点和Ar

  • Java1.8中StringJoiner的使用及源码详析

    前言 StringJoiner是Java里1.8新增的类,主要是帮助我们把一个列表拼接字符串, 或许有一部分人没有接触过. 所以本文将从使用例子入手, 分析StringJoiner的源码. 基本好的同学, 其实只要把这段例子自己运行一下, 自己看看源码就可以了.因为我觉得这个类挺简单的. 没必要看我下面的废话.... public class StringJoinerTest { public static void main(String[] args) { StringJoiner join

  • Spring Security架构以及源码详析

    前言 现在流行的通用授权框架有apache的shiro和Spring家族的Spring Security,在涉及今天的微服务鉴权时,需要利用我们的授权框架搭建自己的鉴权服务,今天总理了Spring Security. Spring Security 主要实现了Authentication(认证,解决who are you? ) 和 Access Control(访问控制,也就是what are you allowed to do?,也称为Authorization).Spring Securit

随机推荐