search
HomeBackend DevelopmentPython TutorialUsage and in-depth use of ThreadLocal class in Java multi-thread programming

ThreadLocal, literally translated as "thread local" or "local thread", if you really think so, you are wrong! In fact, it is a container used to store local variables of threads. I think it should be called ThreadLocalVariable (thread local variable). I really don’t understand why Sun engineers named it this way.

As early as the era of JDK 1.2, java.lang.ThreadLocal was born. It was designed to solve multi-thread concurrency problems, but it was designed to be a bit difficult to use, so it has not been widely used yet. In fact, it is quite useful. If you don’t believe it, let’s take a look at this example.

A serial number generator program may have multiple threads accessing it concurrently at the same time. It is necessary to ensure that the serial number obtained by each thread is incremented and cannot interfere with each other.

First define an interface:

public interface Sequence {

  int getNumber();
}

Every time you call the getNumber() method, you can get a serial number. The next time you call it, the serial number will increase automatically.

Make another thread class:

public class ClientThread extends Thread {

  private Sequence sequence;

  public ClientThread(Sequence sequence) {
    this.sequence = sequence;
  }

  @Override
  public void run() {
    for (int i = 0; i < 3; i++) {
      System.out.println(Thread.currentThread().getName() + " => " + sequence.getNumber());
    }
  }



}

Output the thread name and its corresponding sequence number three times in a row in the thread.

Let’s not use ThreadLocal first and make an implementation class.

public class SequenceA implements Sequence {

  private static int number = 0;

  public int getNumber() {
    number = number + 1;
    return number;
  }

  public static void main(String[] args) {
    Sequence sequence = new SequenceA();

    ClientThread thread1 = new ClientThread(sequence);
    ClientThread thread2 = new ClientThread(sequence);
    ClientThread thread3 = new ClientThread(sequence);

    thread1.start();
    thread2.start();
    thread3.start();
  }
}

The initial value of the sequence number is 0. Three threads are simulated in the main() method. The result after running is as follows:

Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 4
Thread-2 => 5
Thread-2 => 6
Thread-1 => 7
Thread-1 => 8
Thread-1 => 9

Since the thread startup order is random, it is not in the order of 0, 1, 2. This is easy to understand. Why does Thread-2 output 4, 5, and 6 after Thread-0 outputs 1, 2, and 3? Static variables are actually shared between threads! This is the so-called "non-thread safety" problem.

So how to ensure "thread safety"? Corresponding to this case, it means that different threads can have their own static variables. How to implement this? Let’s take a look at another implementation.

public class SequenceB implements Sequence {

  private static ThreadLocal<Integer> numberContainer = new ThreadLocal<Integer>() {
    @Override
    protected Integer initialValue() {
      return 0;
    }
  };

  public int getNumber() {
    numberContainer.set(numberContainer.get() + 1);
    return numberContainer.get();
  }

  public static void main(String[] args) {
    Sequence sequence = new SequenceB();

    ClientThread thread1 = new ClientThread(sequence);
    ClientThread thread2 = new ClientThread(sequence);
    ClientThread thread3 = new ClientThread(sequence);

    thread1.start();
    thread2.start();
    thread3.start();
  }
}

ThreadLocal encapsulates a numberContainer static member variable of type Integer, and the initial value is 0. Looking at the getNumber() method, first get the current value from numberContainer, add 1, then set it to numberContainer, and finally get the current value from numberContainer and return it.

Isn’t it disgusting? But very powerful! Indeed, for a little bit, we might as well think of ThreadLocal as a container, so that the understanding is simple. Therefore, the word Container is deliberately used as a suffix to name the ThreadLocal variable.

What’s the result? Take a look.

Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 1
Thread-2 => 2
Thread-2 => 3
Thread-1 => 1
Thread-1 => 2
Thread-1 => 3

Each thread is independent of each other. It is also a static variable. For different threads, it is not shared, but each thread has its own copy, thus ensuring thread safety. In other words, TheadLocal provides an independent copy for each thread!

After figuring out the principle of ThreadLocal, it is necessary to summarize the API of ThreadLocal. It is actually very simple.

  • public void set(T value): Put the value into the thread local variable
  • public T get(): Get values ​​from thread local variables
  • public void remove(): Removes values ​​from thread local variables (helps JVM garbage collection)
  • protected T initialValue(): Returns the initial value in the thread local variable (default is null)

Why is the initialValue() method protected? Just to remind programmers that this method is implemented by you. Please give this thread local variable an initial value.

After understanding the principles and these APIs, think about it, doesn’t ThreadLocal encapsulate a Map? You can write a ThreadLocal yourself, give it a try.

public class MyThreadLocal<T> {

  private Map<Thread, T> container = Collections.synchronizedMap(new HashMap<Thread, T>());

  public void set(T value) {
    container.put(Thread.currentThread(), value);
  }

  public T get() {
    Thread thread = Thread.currentThread();
    T value = container.get(thread);
    if (value == null && !container.containsKey(thread)) {
      value = initialValue();
      container.put(thread, value);
    }
    return value;
  }

  public void remove() {
    container.remove(Thread.currentThread());
  }

  protected T initialValue() {
    return null;
  }
}

The above is a complete copycat of ThreadLocal, which defines a synchronization Map (why is this? Please think about it yourself), the code should be very easy to read.
Let’s use MyThreadLocal to implement it again.

public class SequenceC implements Sequence {

  private static MyThreadLocal<Integer> numberContainer = new MyThreadLocal<Integer>() {
    @Override
    protected Integer initialValue() {
      return 0;
    }
  };

  public int getNumber() {
    numberContainer.set(numberContainer.get() + 1);
    return numberContainer.get();
  }

  public static void main(String[] args) {
    Sequence sequence = new SequenceC();

    ClientThread thread1 = new ClientThread(sequence);
    ClientThread thread2 = new ClientThread(sequence);
    ClientThread thread3 = new ClientThread(sequence);

    thread1.start();
    thread2.start();
    thread3.start();
  }
}

The above code actually replaces ThreadLocal with MyThreadLocal. That’s it. The running effect is the same as before and is correct.

In fact, ThreadLocal can be a design pattern on its own, depending on how you look at it.

What are the specific use cases for ThreadLocal?

The first thing I want to say is: store JDBC Connection through ThreadLocal to achieve transaction control capabilities.

Let’s keep my usual style and let a demo speak for itself. The user put forward a requirement: when modifying the product price, the operation log needs to be recorded, and when and what was done.

Presumably everyone who has done application systems must have encountered this case, right? There are only two tables in the database: product and log. Using two SQL statements should solve the problem:

update product set price = &#63; where id = &#63;
insert into log (created, description) values (&#63;, &#63;)

But!要确保这两条 SQL 语句必须在同一个事务里进行提交,否则有可能 update 提交了,但 insert 却没有提交。如果这样的事情真的发生了,我们肯定会被用户指着鼻子狂骂:“为什么产品价格改了,却看不到什么时候改的呢?”。

聪明的我在接到这个需求以后,是这样做的:

首先,我写一个 DBUtil 的工具类,封装了数据库的常用操作:

public class DBUtil {
  // 数据库配置
  private static final String driver = "com.mysql.jdbc.Driver";
  private static final String url = "jdbc:mysql://localhost:3306/demo";
  private static final String username = "root";
  private static final String password = "root";

  // 定义一个数据库连接
  private static Connection conn = null;

  // 获取连接
  public static Connection getConnection() {
    try {
      Class.forName(driver);
      conn = DriverManager.getConnection(url, username, password);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return conn;
  }

  // 关闭连接
  public static void closeConnection() {
    try {
      if (conn != null) {
        conn.close();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

里面搞了一个 static 的 Connection,这下子数据库连接就好操作了,牛逼吧!

然后,我定义了一个接口,用于给逻辑层来调用:

public interface ProductService {

  void updateProductPrice(long productId, int price);
}

根据用户提出的需求,我想这个接口完全够用了。根据 productId 去更新对应 Product 的 price,然后再插入一条数据到 log 表中。

其实业务逻辑也不太复杂,于是我快速地完成了 ProductService 接口的实现类:

public class ProductServiceImpl implements ProductService {

  private static final String UPDATE_PRODUCT_SQL = "update product set price = &#63; where id = &#63;";
  private static final String INSERT_LOG_SQL = "insert into log (created, description) values (&#63;, &#63;)";

  public void updateProductPrice(long productId, int price) {
    try {
      // 获取连接
      Connection conn = DBUtil.getConnection();
      conn.setAutoCommit(false); // 关闭自动提交事务(开启事务)

      // 执行操作
      updateProduct(conn, UPDATE_PRODUCT_SQL, productId, price); // 更新产品
      insertLog(conn, INSERT_LOG_SQL, "Create product."); // 插入日志

      // 提交事务
      conn.commit();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      // 关闭连接
      DBUtil.closeConnection();
    }
  }

  private void updateProduct(Connection conn, String updateProductSQL, long productId, int productPrice) throws Exception {
    PreparedStatement pstmt = conn.prepareStatement(updateProductSQL);
    pstmt.setInt(1, productPrice);
    pstmt.setLong(2, productId);
    int rows = pstmt.executeUpdate();
    if (rows != 0) {
      System.out.println("Update product success!");
    }
  }

  private void insertLog(Connection conn, String insertLogSQL, String logDescription) throws Exception {
    PreparedStatement pstmt = conn.prepareStatement(insertLogSQL);
    pstmt.setString(1, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()));
    pstmt.setString(2, logDescription);
    int rows = pstmt.executeUpdate();
    if (rows != 0) {
      System.out.println("Insert log success!");
    }
  }
}

代码的可读性还算不错吧?这里我用到了 JDBC 的高级特性 Transaction 了。暗自庆幸了一番之后,我想是不是有必要写一个客户端,来测试一下执行结果是不是我想要的呢? 于是我偷懒,直接在 ProductServiceImpl 中增加了一个 main() 方法:

public static void main(String[] args) {
  ProductService productService = new ProductServiceImpl();
  productService.updateProductPrice(1, 3000);
}

我想让 productId 为 1 的产品的价格修改为 3000。于是我把程序跑了一遍,控制台输出:

Update product success!
Insert log success!

应该是对了。作为一名专业的程序员,为了万无一失,我一定要到数据库里在看看。没错!product 表对应的记录更新了,log 表也插入了一条记录。这样就可以将 ProductService 接口交付给别人来调用了。

几个小时过去了,QA 妹妹开始骂我:“我靠!我才模拟了 10 个请求,你这个接口怎么就挂了?说是数据库连接关闭了!”。

听到这样的叫声,让我浑身打颤,立马中断了我的小视频,赶紧打开 IDE,找到了这个 ProductServiceImpl 这个实现类。好像没有 Bug 吧?但我现在不敢给她任何回应,我确实有点怕她的。

我突然想起,她是用工具模拟的,也就是模拟多个线程了!那我自己也可以模拟啊,于是我写了一个线程类:

public class ClientThread extends Thread {

  private ProductService productService;

  public ClientThread(ProductService productService) {
    this.productService = productService;
  }

  @Override
  public void run() {
    System.out.println(Thread.currentThread().getName());
    productService.updateProductPrice(1, 3000);
  }
}

我用这线程去调用 ProduceService 的方法,看看是不是有问题。此时,我还要再修改一下 main() 方法:

// public static void main(String[] args) {
//   ProductService productService = new ProductServiceImpl();
//   productService.updateProductPrice(1, 3000);
// }
  
public static void main(String[] args) {
  for (int i = 0; i < 10; i++) {
    ProductService productService = new ProductServiceImpl();
    ClientThread thread = new ClientThread(productService);
    thread.start();
  }
}

我也模拟 10 个线程吧,我就不信那个邪了!

运行结果真的让我很晕、很晕:

Thread-1
Thread-3
Thread-5
Thread-7
Thread-9
Thread-0
Thread-2
Thread-4
Thread-6
Thread-8
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
at com.mysql.jdbc.Util.getInstance(Util.java:386)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1015)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:989)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:975)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:920)
at com.mysql.jdbc.ConnectionImpl.throwConnectionClosedException(ConnectionImpl.java:1304)
at com.mysql.jdbc.ConnectionImpl.checkClosed(ConnectionImpl.java:1296)
at com.mysql.jdbc.ConnectionImpl.commit(ConnectionImpl.java:1699)
at com.smart.sample.test.transaction.solution1.ProductServiceImpl.updateProductPrice(ProductServiceImpl.java:25)
at com.smart.sample.test.transaction.ClientThread.run(ClientThread.java:18)

我靠!竟然在多线程的环境下报错了,果然是数据库连接关闭了。怎么回事呢?我陷入了沉思中。于是我 Copy 了一把那句报错信息,在百度、Google,还有 OSC 里都找了,解答实在是千奇百怪。

我突然想起,既然是跟 Connection 有关系,那我就将主要精力放在检查 Connection 相关的代码上吧。是不是 Connection 不应该是 static 的呢?我当初设计成 static 的主要是为了让 DBUtil 的 static 方法访问起来更加方便,用 static 变量来存放 Connection 也提高了性能啊。怎么搞呢?

于是我看到了 OSC 上非常火爆的一篇文章《ThreadLocal 那点事儿》,终于才让我明白了!原来要使每个线程都拥有自己的连接,而不是共享同一个连接,否则线程1有可能会关闭线程2的连接,所以线程2就报错了。一定是这样!

我赶紧将 DBUtil 给重构了:

public class DBUtil {
  // 数据库配置
  private static final String driver = "com.mysql.jdbc.Driver";
  private static final String url = "jdbc:mysql://localhost:3306/demo";
  private static final String username = "root";
  private static final String password = "root";

  // 定义一个用于放置数据库连接的局部线程变量(使每个线程都拥有自己的连接)
  private static ThreadLocal<Connection> connContainer = new ThreadLocal<Connection>();

  // 获取连接
  public static Connection getConnection() {
    Connection conn = connContainer.get();
    try {
      if (conn == null) {
        Class.forName(driver);
        conn = DriverManager.getConnection(url, username, password);
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      connContainer.set(conn);
    }
    return conn;
  }

  // 关闭连接
  public static void closeConnection() {
    Connection conn = connContainer.get();
    try {
      if (conn != null) {
        conn.close();
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      connContainer.remove();
    }
  }
}

我把 Connection 放到了 ThreadLocal 中,这样每个线程之间就隔离了,不会相互干扰了。

此外,在 getConnection() 方法中,首先从 ThreadLocal 中(也就是 connContainer 中) 获取 Connection,如果没有,就通过 JDBC 来创建连接,最后再把创建好的连接放入这个 ThreadLocal 中。可以把 ThreadLocal 看做是一个容器,一点不假。

同样,我也对 closeConnection() 方法做了重构,先从容器中获取 Connection,拿到了就 close 掉,最后从容器中将其 remove 掉,以保持容器的清洁。

这下应该行了吧?我再次运行 main() 方法:

Thread-0
Thread-2
Thread-4
Thread-6
Thread-8
Thread-1
Thread-3
Thread-5
Thread-7
Thread-9
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!

总算是解决了

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Python vs. C  : Learning Curves and Ease of UsePython vs. C : Learning Curves and Ease of UseApr 19, 2025 am 12:20 AM

Python is easier to learn and use, while C is more powerful but complex. 1. Python syntax is concise and suitable for beginners. Dynamic typing and automatic memory management make it easy to use, but may cause runtime errors. 2.C provides low-level control and advanced features, suitable for high-performance applications, but has a high learning threshold and requires manual memory and type safety management.

Python vs. C  : Memory Management and ControlPython vs. C : Memory Management and ControlApr 19, 2025 am 12:17 AM

Python and C have significant differences in memory management and control. 1. Python uses automatic memory management, based on reference counting and garbage collection, simplifying the work of programmers. 2.C requires manual management of memory, providing more control but increasing complexity and error risk. Which language to choose should be based on project requirements and team technology stack.

Python for Scientific Computing: A Detailed LookPython for Scientific Computing: A Detailed LookApr 19, 2025 am 12:15 AM

Python's applications in scientific computing include data analysis, machine learning, numerical simulation and visualization. 1.Numpy provides efficient multi-dimensional arrays and mathematical functions. 2. SciPy extends Numpy functionality and provides optimization and linear algebra tools. 3. Pandas is used for data processing and analysis. 4.Matplotlib is used to generate various graphs and visual results.

Python and C  : Finding the Right ToolPython and C : Finding the Right ToolApr 19, 2025 am 12:04 AM

Whether to choose Python or C depends on project requirements: 1) Python is suitable for rapid development, data science, and scripting because of its concise syntax and rich libraries; 2) C is suitable for scenarios that require high performance and underlying control, such as system programming and game development, because of its compilation and manual memory management.

Python for Data Science and Machine LearningPython for Data Science and Machine LearningApr 19, 2025 am 12:02 AM

Python is widely used in data science and machine learning, mainly relying on its simplicity and a powerful library ecosystem. 1) Pandas is used for data processing and analysis, 2) Numpy provides efficient numerical calculations, and 3) Scikit-learn is used for machine learning model construction and optimization, these libraries make Python an ideal tool for data science and machine learning.

Learning Python: Is 2 Hours of Daily Study Sufficient?Learning Python: Is 2 Hours of Daily Study Sufficient?Apr 18, 2025 am 12:22 AM

Is it enough to learn Python for two hours a day? It depends on your goals and learning methods. 1) Develop a clear learning plan, 2) Select appropriate learning resources and methods, 3) Practice and review and consolidate hands-on practice and review and consolidate, and you can gradually master the basic knowledge and advanced functions of Python during this period.

Python for Web Development: Key ApplicationsPython for Web Development: Key ApplicationsApr 18, 2025 am 12:20 AM

Key applications of Python in web development include the use of Django and Flask frameworks, API development, data analysis and visualization, machine learning and AI, and performance optimization. 1. Django and Flask framework: Django is suitable for rapid development of complex applications, and Flask is suitable for small or highly customized projects. 2. API development: Use Flask or DjangoRESTFramework to build RESTfulAPI. 3. Data analysis and visualization: Use Python to process data and display it through the web interface. 4. Machine Learning and AI: Python is used to build intelligent web applications. 5. Performance optimization: optimized through asynchronous programming, caching and code

Python vs. C  : Exploring Performance and EfficiencyPython vs. C : Exploring Performance and EfficiencyApr 18, 2025 am 12:20 AM

Python is better than C in development efficiency, but C is higher in execution performance. 1. Python's concise syntax and rich libraries improve development efficiency. 2.C's compilation-type characteristics and hardware control improve execution performance. When making a choice, you need to weigh the development speed and execution efficiency based on project needs.

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

Powerful PHP integrated development environment

mPDF

mPDF

mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools