当前位置:网站首页>Proficient in high concurrency and multithreading, but can't use ThreadLocal?

Proficient in high concurrency and multithreading, but can't use ThreadLocal?

2020-11-08 16:59:02 Cai Bucai

Hello everyone , I'm a small dish , A small dish eager to be Cai Bucai in the Internet industry . Can be soft or just , Praise makes you soft , White whoring is just ! Dead man ~ After watching, please give me a third company !

This paper mainly introduces ThreadLocal Use

If necessary , You can refer to

If help , Don't forget give the thumbs-up *

The official account of WeChat has been opened , A small dish is a good memory , If you don't pay attention to it, remember to pay attention to it !

We mentioned earlier in the concurrency series ThreadLocal Class and basic usage , Let's take a look at ThreadLocal How is it used !

ThreadLocal brief introduction

Concept

ThreadLocal Class is used to provide local variables within a thread . This variable is accessed in a multithreaded environment (get and set Method access ) It can ensure that the variables of each thread are relatively independent of the variables in other threads . ThreadLocal Examples are usually private static Type of , Used to correlate threads and contexts .

effect

  • To transfer data

Provide local variables inside the thread . Can pass ThreadLocal In the same thread , Passing common variables in different components .

  • Threads concurrent

It is suitable for multithreading concurrency .

  • Thread isolation

The variables of each thread are independent , Will not affect each other .

ThreadLocal actual combat

1. Common methods

  • ThreadLocal ()

Construction method , Create a ThreadLocal object

  • void set (T value)

Set the local variable of the current thread binding

  • T get ()

Get the local variable of the current thread binding

  • void remove ()

Remove the local variables bound by the current thread

2. Why use ThreadLocal

First, let's look at a set of code scenarios under concurrent conditions :

 @Data
 public class ThreadLocalTest {
  private String name;
 ​
  public static void main(String[] args) {
  ThreadLocalTest tmp = new ThreadLocalTest();
  for (int i = 0; i < 4; i++) {
  Thread thread = new Thread(() -> {
  tmp.setName(Thread.currentThread().getName());
  System.out.println(Thread.currentThread().getName() +
  "t  Get the data :" + tmp.getName());
  });
  thread.setName("Thread-" + i);
  thread.start();
  }
  }
 }

Our ideal code output would be like this :

 /** OUTPUT **/
 Thread-0   Get the data :Thread-0
 Thread-1   Get the data :Thread-1
 Thread-2   Get the data :Thread-2
 Thread-3   Get the data :Thread-3

But actually the output is like this :

 /** OUTPUT **/
 Thread-0   Get the data :Thread-1
 Thread-3   Get the data :Thread-3
 Thread-1   Get the data :Thread-1
 Thread-2   Get the data :Thread-2

It doesn't matter if the order is out of order , But we can see that Thread-0 The value this thread gets is Thread-1

From the results, we can see that there are exceptions when multiple threads access the same variable , This is because there is no isolation of data between threads !

Problems with concurrent threads ? Then lock it and it's done ! At this time, you wrote the following code by dividing three by five :

 @Data
 public class ThreadLocalTest {
 ​
  private String name;
 ​
  public static void main(String[] args) {
  ThreadLocalTest tmp = new ThreadLocalTest();
  for (int i = 0; i < 4; i++) {
  Thread thread = new Thread(() -> {
  synchronized (tmp) {
  tmp.setName(Thread.currentThread().getName());
  System.out.println(Thread.currentThread().getName() 
  + "t" + tmp.getName());
  }
  });
  thread.setName("Thread-" + i);
  thread.start();
  }
  }
 }
 /** OUTPUT **/
 Thread-2 Thread-2
 Thread-3 Thread-3
 Thread-1 Thread-1
 Thread-0 Thread-0

In terms of the results , Locking seems to solve the above problems , however synchronized Commonly used in the problem of multithreading data sharing , Instead of multithreading data isolation . Use here synchronized Although the problem has been solved , But it's kind of inappropriate , also synchronized It's a heavyweight lock , In order to achieve multi-threaded data isolation, rashly add synchronized, It also affects performance .

The method of locking has also been denied , So how to solve ? Better use ThreadLocal Try it with a knife :

 public class ThreadLocalTest {
 ​
  private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
 ​
  public String getName() {
  return threadLocal.get();
  }
 ​
  public void setName(String name) {
  threadLocal.set(name);
  }
 ​
  public static void main(String[] args) {
  ThreadLocalTest tmp = new ThreadLocalTest();
  for (int i = 0; i < 4; i++) {
  Thread thread = new Thread(() -> {
  tmp.setName(Thread.currentThread().getName());
  System.out.println(Thread.currentThread().getName() + 
  "t  Get the data :" + tmp.getName());
  });
  thread.setName("Thread-" + i);
  thread.start();
  }
  }
 }

Before looking at the output , Let's take a look at the changes in the code

First of all, one more private static Embellished ThreadLocal , And then in setName When , We are actually going to ThreadLocal There's data in there , stay getName When , We are in ThreadLocal Get data inside . I feel that the operation is very simple , But can this really achieve data isolation between threads , Let's look at the results again :

 /** OUTPUT **/
 Thread-1   Get the data :Thread-1
 Thread-2   Get the data :Thread-2
 Thread-0   Get the data :Thread-0
 Thread-3   Get the data :Thread-3

From the result, we can see that each thread can get the corresponding data .ThreadLocal It has also solved the problem of data isolation between multithreads .

So let's summarize , Why use ThreadLocal, And synchronized What's the difference

  • synchronized

principle : The synchronization mechanism adopts " Time for space " The way , Only one variable is provided , Queue different threads to access

Focus on : Multiple threads access resources synchronously

  • ThreadLocal

principle : ThreadLocal use " Space for time " The way , A copy of the variable is provided for each thread , In order to achieve simultaneous access without interference

Focus on : In multithreading, data between each thread is isolated from each other

3. internal structure

From the above case, we can see that ThreadLocal The two main methods are set() and get()

Let's guess , If we were to design ThreadLocal , How should we design , Whether there will be such an idea : Every ThreadLocal All create a Map, And then use threads as Map Of key, The local variable to be stored as Map Of value , In this way, the effect of local variable isolation of each thread can be achieved .

The idea is also true , In the early ThreadLocal That's how it's designed , But in JDK 8 Then the design was changed , as follows :

design process :

  1. Every Thread There is a... Inside the thread ThreadLocalMap
  2. ThreadLocalMap It's stored in ThreadLocal The object is key , The thread variable is value
  3. Thread Inside Map By ThreadLocal Maintenance of , from ThreadLocal Responsible for providing Map Set and get the variable value of the thread
  4. For different threads , Every time you get a copy value , Other threads cannot get the copy value of the thread , This will result in isolation of copies , Mutual interference

notes : Each thread must have its own map, But this class is just a normal Java class , It didn't happen Map Interface , But it's similar to Map Similar functions .

This seems to be more complicated than we thought before , What are the benefits of doing so ?

  • Every Map Stored Entry The number will be less , Because the amount of storage before was by Thread It's the number of , Now it's up to ThreadMap It's the number of , In actual development ,ThreadLocal The number is less than Thread The number of .
  • When Thread After destruction , Corresponding ThreadLocalMap It will be destroyed with it , Can reduce the use of memory

4. Source code analysis

First of all, let's look at ThreadLocalMap Who are the members of :

If you've seen it HashMap Source code , I'm sure I'm familiar with them , among :

  • INITIAL_CAPACITY: Initial capacity , Must be 2 The whole power of
  • table: Storing data table
  • size: Array entries The number of , Used to judge table Whether the current usage exceeds the threshold
  • threshold: Threshold for capacity expansion , When the table usage is greater than it, the capacity will be expanded

ThreadLocals

Thread Class has a type of ThreadLocal.ThreadLocalMap Variable of type ThreadLocals , This is used to save the private data of each thread .

ThreadLocalMap

ThreadLocalMap yes ThreadLocal The inner class of , Each data use Entry preservation , Among them Entry Store with a key value pair , The key is ThreadLocal References to .

We can see Entry Inherited from WeakReference, This is because if it's a strong reference , Even if ThreadLocal Set to null,GC It won't recycle , because ThreadLocalMap There's a strong reference to it .

Without manually deleting this Entry as well as CurrentThread Still running , There is always a strong reference chain threadRef -> currentThread -> threadLocalMap -> entry,Entry It won't be recycled (Entry It includes ThreadLocal Instance and value), Lead to Entry Memory leak .

Does that mean that if you use Weak reference , It won't cause Memory leak Well , That's not true .

Because if we don't manually delete Entry Under the circumstances , here Entry Medium key == null, There is no strong reference to threaLocal example , therefore threadLocal You can be gc Recycling , however value Will not be recycled , And this one value Will never be visited , So it will lead to Memory leak

Let's take a look ThreadLocalMap Some of the core methods :

set Method

First of all, let's take a look at the source code :

 public void set(T value) {
  //  Get the current thread object 
  Thread t = Thread.currentThread();
  //  Gets the... Maintained in this thread object ThreadLocalMap object 
  ThreadLocalMap map = getMap(t);
  //  Judge map Whether there is 
  if (map != null)
  //  If it exists, call map.set Set this entity entry
  map.set(this, value);
  else
  //  If the current thread does not exist ThreadLocalMap Object calls createMap Conduct ThreadLocalMap Object initialization 
  //  And will  t( Current thread ) and value(t Corresponding value ) As the first entry Store in ThreadLocalMap in 
  createMap(t, value);
 }
 ​
 ThreadLocalMap getMap(Thread t) {
  return t.threadLocals;
 }
 ​
 void createMap(Thread t, T firstValue) {
  // there this This method is called threadLocal
  t.threadLocals = new ThreadLocalMap(this, firstValue);
 }

Execute the process :

  • First, get the current thread , And get one according to the current thread map
  • If you get map Not empty , Set the parameter to map in ( At present ThreadLocal As a reference to key
  • If Map It's empty , Then create for the thread map , And set the initial value

get Method

Source code is as follows :

 public T get() {
  //  Get the current thread object 
  Thread t = Thread.currentThread();
  //  Gets the... Maintained in this thread object ThreadLocalMap object 
  ThreadLocalMap map = getMap(t);
  //  If so map There is 
  if (map != null) {
  //  With the current ThreadLocal  by  key, call getEntry Get the corresponding storage entity e
  ThreadLocalMap.Entry e = map.getEntry(this);
  //  Yes e Judge empty  
  if (e != null) {
  @SuppressWarnings("unchecked")
  //  Get the storage entity  e  Corresponding  value value 
  //  That is, the current thread we want corresponds to this ThreadLocal Value 
  T result = (T)e.value;
  return result;
  }
  }
  return setInitialValue();
 }
 ​
 private T setInitialValue() {
  //  call initialValue Get the initialization value 
  //  This method can be overridden by subclasses ,  If you don't override the default return null
  T value = initialValue();
  //  Get the current thread object 
  Thread t = Thread.currentThread();
  //  Gets the... Maintained in this thread object ThreadLocalMap object 
  ThreadLocalMap map = getMap(t);
  //  Judge map Whether there is 
  if (map != null)
  //  If it exists, call map.set Set this entity entry
  map.set(this, value);
  else
  //  If the current thread does not exist ThreadLocalMap Object calls createMap Conduct ThreadLocalMap Object initialization 
  //  And will  t( Current thread ) and value(t Corresponding value ) As the first entry Store in ThreadLocalMap in 
  createMap(t, value);
  //  Returns the set value value
  return value;
 }

Execute the process :

  • First, get the current thread , Get a from the current thread map
  • If you get map Not empty , It's in map China and Israel ThreadLocal As a reference to key Come on map Get the corresponding Entry entry , Otherwise jump to Step four
  • If Entry entry Not empty , Then return to entry.value , Otherwise jump to Step four
  • map Empty or entry It's empty , Through initialValue Function to get the initial value value , And then use ThreadLocal References to and value As firstKey and firstValue Create a new map

remove Method

Source code is as follows :

 ​
 public void remove() {
  //  Gets the... Maintained in the current thread object ThreadLocalMap object 
  ThreadLocalMap m = getMap(Thread.currentThread());
  //  If so map There is 
  if (m != null)
  //  If it exists, call map.remove
  m.remove(this);
 }
 //  At present ThreadLocal by key Delete the corresponding entity entry
 private void remove(ThreadLocal<?> key) {
  Entry[] tab = table;
  int len = tab.length;
  int i = key.threadLocalHashCode & (len-1);
  for (Entry e = tab[i];
  e != null;
  e = tab[i = nextIndex(i, len)]) {
  if (e.get() == key) {
  e.clear();
  expungeStaleEntry(i);
  return;
  }
  }
 }

Execute the process :

  • First, get the current thread , And get one according to the current thread map
  • If you get map Not empty , Remove the current ThreadLocal The object corresponds to entry

initialValue Method

Source code is as follows :

 protected T initialValue() {
  return null;
 }

In the source code, we can see that this method simply returns null , This method is the first time a thread passes through get () Method to access the thread ThreadLocal Called when , Only the thread calls first set () Method does not call initialValue () Method , Usually , This method can be called at most once .

If you want to ThreadLocal Thread local variables have a division of null The initial value beyond , Then you have to subclass Inherit ThreadLocal To rewrite this method , It can be implemented through anonymous inner classes .

【END】

This article ThreadLocal That's it , I hope that the friends who read here can gain something .

The road is long , Xiaocai seeks with you !

 I don't like it after reading it , All bad guys

Try harder today , Tomorrow, you will be able to say less of a word to ask for help !

I'm a small dish , A man who studies with you .

The official account of WeChat has been opened , A small dish is a good memory , If you don't pay attention to it, remember to pay attention to it !

版权声明
本文为[Cai Bucai]所创,转载请带上原文链接,感谢