Android技术---ThreadLocal详解
前言
不管是平时开发,或者是阅读别人的代码关于多线程的时候。我们总会遇到这个ThreadLocal。
今天算是偶尔也和大家一起来说说Java基础的东西。
ThreadLocal从字面的意思来说其实就是一个线程局部变量,
情景
我们假想一个情景,有3个线程,A线程和B线程,还有我们的主线程。
有一个数字的对象在主线程里,然后A线程和B线程一起读取做一些操作
先画个图解释一下,再上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| package com.martinhan.zeroone;
public class Num { private int value; public Num() { super(); }
public Num(int value) { super(); this.value = value; }
public int getValue() { return value; }
public void setValue(int value) { this.value = value; } public void addValue(int value) { this.value += value; } }
package com.martinhan.zeroone;
public class ThreadLocalTest1 { public static void main(String[] args) { Num num = new Num(100); Thread aThread = new Thread(){ @Override public void run() { try { Thread.sleep(100L); num.addValue(200); System.out.println("thread ---" + Thread.currentThread() + " value is " + num.getValue()); } catch (InterruptedException e) { e.printStackTrace(); }; } }; Thread bThread = new Thread(){ @Override public void run() { try { Thread.sleep(100L); num.addValue(200); System.out.println("thread ---" + Thread.currentThread() + " value is " + num.getValue()); } catch (InterruptedException e) { e.printStackTrace(); }; } }; aThread.start(); bThread.start(); } }
|
执行结果如下
1 2
| thread ---Thread[Thread-0,5,main] value is 300 thread ---Thread[Thread-1,5,main] value is 300
|
其实正常情况下,我们当然不希望是这种结果了。
A线程希望改完了之后,值是300
B线程希望改完了之后值才是500
ThreadLocal引入
此时此刻,我们就想,可不可以这样呢,我们自己做一个HashMap,key放线程的id,然后value放各个线程需要的值。
有了这个机制,我们就可以实现各个线程其实对应的实际Num对象并不是一个了。
用语言来讲的话可能还是不太形象,画个图
然后我们用代码来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| package com.martinhan.zeroone;
import java.util.HashMap;
public class Num2 { private HashMap<Long,Integer> map = new HashMap<Long,Integer>(); public Num2() { super(); }
public Num2(int value) { super(); this.map.put(Thread.currentThread().getId(), value); }
public Integer getValue() { return map.get(Thread.currentThread().getId()); }
public synchronized void setValue(int value) { this.map.put(Thread.currentThread().getId(), value); } public synchronized void addValue(int value) { if(map.containsKey(Thread.currentThread().getId()) == false) { return ; } int oldvalue = map.get(Thread.currentThread().getId()); map.put(Thread.currentThread().getId(), oldvalue + value); } }
package com.martinhan.zeroone;
public class ThreadLocalTest2 { public static void main(String[] args) { Num2 num = new Num2(); Thread aThread = new Thread(){ @Override public void run() { try { Thread.sleep(100L); num.setValue(100); num.addValue(200); System.out.println("thread ---" + Thread.currentThread() + " value is " + num.getValue()); } catch (InterruptedException e) { e.printStackTrace(); }; } }; Thread bThread = new Thread(){ @Override public void run() { try { Thread.sleep(100L); num.setValue(100); num.addValue(400); System.out.println("thread ---" + Thread.currentThread() + " value is " + num.getValue()); } catch (InterruptedException e) { e.printStackTrace(); }; } }; aThread.start(); bThread.start(); } }
|
执行结果如下
1 2
| thread ---Thread[Thread-1,5,main] value is 500 thread ---Thread[Thread-0,5,main] value is 300
|
setValue,addValue方法做了线程同步,因为HashMap本身就是不安全的。这次的执行结果就对了。
按照了我们的图示,这次每个线程都操作自己的数据。然后不会有线程不安全的情况了。
可能有人说,加同步不就好了么,其实不是想为大家一起说下ThreadLocal嘛,举个例子
ThreadLocal
ThreadLocal其实做的事情就大概如此了,只是大概如此,如果需要深究细节,需要去仔细看源码。
以上的代码,还有几处不完善,那个HashMap里的对象会一直存在,他并不会随着线程的结束而删除。
在Java源码里,真正的map是在Thread里面的,
先举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package com.martinhan.zeroone;
public class ThreadLocalTest3 {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); static int value = 100;
public static void main(String[] args) throws InterruptedException { Thread aThread = new Thread(){ @Override public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } threadLocal.set(value + 200); System.out.println("thread ---" + Thread.currentThread() + " value is " + threadLocal.get()); } };
Thread bThread = new Thread(){ @Override public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } threadLocal.set(value + 400); System.out.println("thread ---" + Thread.currentThread() + " value is " + threadLocal.get()); } }; aThread.start(); bThread.start(); } }
|
然后我们从threadLocal.set方法来读,实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); } }
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
|
在线程退出的时候有如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private void exit() { if (group != null) { group.threadTerminated(this); group = null; } target = null; threadLocals = null; inheritableThreadLocals = null; inheritedAccessControlContext = null; blocker = null; uncaughtExceptionHandler = null; }
|
这里面手动置空了threadLocals成员变量
源码地址
源码链接
写在最后
ThreadLocal大体的思路如此,个人觉得懂了两个图,就算可以说大致懂了,然后读源码
希望读者有问题,可以和我探讨,喜欢一起交流技术问题
关于我
个人博客:MartinHan的小站
博客网站:hanhan12312的专栏
知乎:MartinHan01