0
点赞
收藏
分享

微信扫一扫

ThreadLocal 学习笔记

言午栩 2022-09-14 阅读 169


文章目录

  • ​​对 Threadlocal 理解​​
  • ​​官方定义​​
  • ​​作用​​
  • ​​容易误解的地方​​
  • ​​总结​​
  • ​​ThreadLocal 源码总结​​
  • ​​Android 中的 ThreadLocal 应用​​
  • ​​参考链接​​

对 Threadlocal 理解

​ThreadLocal​​ 是为了解决线程间同步而创建的一个新的思路。简单来说就是每个线程都保存一个变量副本

如果在 ​​Thread​​​ 内部定义一个 ​​field​​ 变量,也可以解决这个问题。

这样就需要定义一个新的 ​​Thread​​​ 类,来解决这个问题。每一次一个新的变量都需要这个 ​​case​​​,but,实际这个新的类,与 ​​thread​​ 本身并没有关系。

所以最好有一种方式,可以解决同步的问题,并且每个thread里面都有一份变量,但是不需要重新定义一个 ​​thread​​ 类,来集成这个功能。

官方定义


/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*
* <p>For example, the class below generates unique identifiers local to each
* thread.
* A thread's id is assigned the first time it invokes {@code ThreadId.get()}
* and remains unchanged on subsequent calls.
*/

核心意思就是

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

作用

移步:​​ThreadLocal就是这么简单​​

  1. JDBC 管理 Connection
  2. 避免一些参数传递 --> 不同网站传递不同的​​Cookie​​​ 和​​Session​
  3. 格式化日期的类用​​threadlocal​​,因为它不是线程安全的,不是线程安全的类在多线程环境下使用会出现意向不到的结果。多线程环境下使用,格式会被清掉,从而出现空指针错误。

ThreadLocal 使用方便,因为不用上下文切换,但会造成一些性能上的损失。

容易误解的地方

ThreadLocal 设计的目的就是为了能够在当前线程中有属于自己的变量,并不是为了解决并发或者共享变量的问题

总结

  • ThreadLocal 并不解决线程间共享数据的问题
  • ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
  • 每个线程持有一个 Map 并维护了 ThreadLocal 对象与具体实例的映射,该 Map 由于只被持有它的线程访问,故不存在线程安全以及锁的问题
  • ThreadLocalMap 的 Entry 对 ThreadLocal 的引用为弱引用,避免了 ThreadLocal 对象无法被回收的问题
  • ThreadLocalMap 的 set 方法通过调用 replaceStaleEntry 方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏
  • ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景

ThreadLocal 源码总结

  • 每个​​Thread​​​ 维护着一个​​ThreadLocalMap​​ 的引用
  • ​ThreadLocalMap​​​ 是​​ThreadLocal​​​ 的内部类,用​​Entry​​ 来进行存储
  • 调用​​ThreadLocal​​​ 的​​set()​​​ 方法时,实际上就是往​​ThreadLocalMap​​​ 设置值,​​key​​​ 是​​ThreadLocal​​ 对象,值是传递进来的对象
  • 调用​​ThreadLocal​​​ 的​​get()​​​ 方法时,实际上就是往​​ThreadLocalMap​​​ 获取值,​​key​​​ 是​​ThreadLocal​​ 对象
  • ​ThreadLocal​​​ 本身并不存储值,它只是作为一个​​key​​​ 来让线程从​​ThreadLocalMap​​​ 获取​​value​​。

Android 中的 ThreadLocal 应用

​ThreadLocal​​ 就是这种思路。

public final class Looper {
/*
* API Implementation Note:
*
* This class contains the code required to set up and manage an event loop
* based on MessageQueue. APIs that affect the state of the queue should be
* defined on MessageQueue or Handler rather than on Looper itself. For example,
* idle handlers and sync barriers are defined on the queue whereas preparing the
* thread, looping, and quitting are defined on the looper.
*/

private static final String TAG = "Looper";

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class

final MessageQueue mQueue;
final Thread mThread;

private Printer mLogging;

/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}

private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

/**
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}

/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}

msg.target.dispatchMessage(msg);

if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}

msg.recycleUnchecked();
}
}

Looper

​Looper​​​ 是 ​​android​​​ 最核心的技术之一,消息机制。是整个 ​​UI​​​ 层驱动的核心。它的思路如下,每个线程都可以有一个自己的消息队列。这个队列默认是没有创建的,(​​mainthread​​ 是系统创建的。)

我们看到,这个类里面就有

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

也就是每个线程都有一个 ​​Looper​​ 对象。

参考链接

  • ​​源码分析​​
  • ​​Java进阶(七)正确理解Thread Local的原理与适用场景​​
  • ​​ThreadLocal就是这么简单​​


举报

相关推荐

0 条评论