首先说明,现在我坚定的认为这两个东西并不是像有的博主说的是用来解决 “共享变量的访问冲突”的。那么他们是用来干嘛的呢?顾名思义他们都和当前线程的本地变量有关。
1、ThreadLocal
a)先来看一段代码:
package net.csdn.blog;
import static java.lang.Thread.sleep;
/**
* Created by nostalie.zhang on 2016/12/22.
* 实现Runnable接口用以创建线程
*/
class TestClient5 implements Runnable {
private int i = 0;
public void run() {
while (true) {
//这里操作了共享数据i
if (i < 10) {
System.out.println(Thread.currentThread().getName() + "---" + i);
i++;
}else{
break;
}
//为了让效果更加明显使用了sleep();
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TestThreadLocal5 {
public static void main(String[] args) {
TestClient5 testClient5= new TestClient5();
//创建了两个线程
Thread thread1 = new Thread(testClient5);
Thread thread2 = new Thread(testClient5);
//开启线程
thread1.start();
thread2.start();
}
}
运行结果:数据出现了重复,这是并发线程访问共享数据带来的问题
Thread-1---0
Thread-0---1
Thread-1---2
Thread-0---3
Thread-1---4
Thread-0---4
Thread-1---6
Thread-0---6
Thread-0---8
Thread-1---8
Process finished with exit code 0
b)为了解决这个问题我们可以使用同步代码块如下:
package com.qunar.flight;
import static java.lang.Thread.sleep;
/**
* Created by nostalie.zhang on 2016/12/22.
*/
class TestClient2 implements Runnable {
private int i = 0;
public void run() {
while (true) {
//同步代码块;jdk5以后也可以用lock来实现
synchronized (this) {
if (i < 10) {
System.out.println(Thread.currentThread().getName() + "---" + i);
i++;
}else{
break;
}
}
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TestThreadLocal2 {
public static void main(String[] args) {
TestClient2 testClient2 = new TestClient2();
Thread thread1 = new Thread(testClient2);
Thread thread2 = new Thread(testClient2);
thread1.start();
thread2.start();
}
}
运行结果:数据正常
Thread-0---0
Thread-1---1
Thread-0---2
Thread-1---3
Thread-1---4
Thread-0---5
Thread-1---6
Thread-0---7
Thread-0---8
Thread-1---9
Process finished with exit code 0
c)来看看使用ThreadLocal的效果
package com.qunar.flight;
/**
* Created by nostalie.zhang on 2016/12/22.
*/
class TestClient implements Runnable {
private int i=0;
//创建ThreadLocal实例
public static ThreadLocal<Integer> ThreadID = new ThreadLocal<Integer>();
public void run() {
//保存i到ThreadLocal实例中
ThreadID.set(i);
while(ThreadID.get()<10) {
//打印保存的i
System.out.println(Thread.currentThread().getName() + "---" + ThreadID.get());
//保存i+1
ThreadID.set(ThreadID.get()+1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TestThreadLocal {
public static void main(String[] args) {
TestClient testClient=new TestClient();
Thread thread1=new Thread(testClient);
Thread thread2=new Thread(testClient);
thread1.start();
thread2.start();
while(thread1.isAlive()||thread2.isAlive()){}
System.out.println("testClient的变量i"+"---"+testClient.getI());
}
}
运行结果:可以看出每一个线程都打印了从0到9。看起来就像每一个线程都把共享变量初值复制了一份保存在自己的线程空间中,而以后的操作都只针对自己保存的变量,而不去操作共享变量。注意我说的是好像
Thread-1---0
Thread-0---0
Thread-1---1
Thread-0---1
Thread-1---2
Thread-0---2
Thread-1---3
Thread-0---3
Thread-1---4
Thread-0---4
Thread-1---5
Thread-0---5
Thread-1---6
Thread-0---6
Thread-1---7
Thread-0---7
Thread-1---8
Thread-0---8
Thread-1---9
Thread-0---9
testClient的变量i---0
Process finished with exit code 0
d)继续来看这段代码:
package com.qunar.flight;
/**
* Created by nostalie.zhang on 2016/12/22.
*/
class MyInteger4 {
private int i = 0;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
class TestClient4 implements Runnable {
private MyInteger4 i = new MyInteger4();
public static ThreadLocal<MyInteger4> ThreadID = new ThreadLocal<MyInteger4>();
public void run() {
//依旧使用了ThreadLocal,但传入的不在是基础类型而是引用类型。
ThreadID.set(i);
while (true) {
if (ThreadID.get().getI() < 10) {
System.out.println(Thread.currentThread().getName() + "---" + ThreadID.get().getI());
if(Thread.currentThread().getName().equals("t1")){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
MyInteger4 myInteger = ThreadID.get();
myInteger.setI(myInteger.getI() + 1);
ThreadID.set(myInteger);
} else {
break;
}
}
}
}
public class TestThreadLocal4 {
public static void main(String[] args) {
TestClient4 testClient4 = new TestClient4();
Thread thread1 = new Thread(testClient4);
Thread thread2 = new Thread(testClient4);
thread1.setName("t1");
thread1.setPriority(10);
thread2.setName("t2");
thread1.start();
thread2.start();
}
}
运行结果如下:从这里我们可以看出,如果ThreadLocal传入的是引用类型,内部保存的只是对堆区实体的引用,而不是将堆区的对象实体复制一份。并且ThreadLocal无法保证线程安全。
t1---0
t2---0
t2---1
t2---2
t2---3
t2---4
t2---5
t2---6
t2---7
t2---8
t2---9
Process finished with exit code 0
e)我们去看一下ThreadLocal的源码实现
-ThreadLocal有一个内部类ThreadLocalMap,而Thread维护了一个ThreadLocalMap的引用。
-看一下ThreadLocal的set()方法:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程中的ThreadLocalMap的引用
ThreadLocalMap map = getMap(t);
if (map != null)
//如果不为空则将ThreadLocal作为key,传入参数作为value保存到ThreadLocalMap的引用中
map.set(this, value);
else
//第一次set()引用为null,则初始化一个map并保存值,将map的引用交给Thread。
createMap(t, value);
}
所以真实情况就是这个样子每一个线程都有一个这样的Map<ThreadLocal,T> map
(ThreadLocal.ThreadLocalMap threadLocals = null;
其实源码是这样的,只是为了方便说明写成这样)当你调用ThreadLocal的get()方法时就是以当前的ThreadLocal为key查找对用的value。(实现可以去看源码),set()就是以当前ThreadLocal为Key,参数为value保存到map中。
-所以一个ThreadLocal实例只是一个具体线程中的本地变量的一个索引。应用到web程序中,可以实现变量在DAO层,service层,Controller层的传递,并且这种传递在线程之间是隔绝的。也正是因为这种隔绝让很多人以为ThreadLocal解决了共享变量的冲突问题。
-ThreadLocal实现了线程内部的单例
2、InheritableThreadLocal(顾名思义:可继承的线程本地变量)
它与ThreadLocal非常相似,只不过InheritableThreadLocal的变量可以从父线程传递给子线程
看代码:
package com.qunar.flight.threadLocal;
/**
* Created by nostalie.zhang on 2016/12/22.
*/
//使用了Junit的测试类
import junit.framework.TestCase;
import static java.lang.System.out;
public class InheritableThreadLocalTest2 extends TestCase {
public void testThreadLocal() {
final ThreadLocal<StringBuilder> local = new ThreadLocal<StringBuilder>();
work(local);
}
public void testInheritableThreadLocal() {
final ThreadLocal<StringBuilder> local = new InheritableThreadLocal<StringBuilder>();
work(local);
}
private void work(final ThreadLocal<StringBuilder> local) {
local.set(new StringBuilder("a"));
out.println(Thread.currentThread() + "," + local.get());
Thread t = new Thread(new Runnable() {
public void run() {
out.println(Thread.currentThread() + "," + local.get());
local.set(new StringBuilder("b"));
out.println(Thread.currentThread() + "," + local.get());
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
out.println(Thread.currentThread() + "," + local.get());
}
}
两个测试结果如下:不做过多说明
testThreadLocal()
Thread[main,5,main],a
Thread[Thread-0,5,main],null
Thread[Thread-0,5,main],b
Thread[main,5,main],a
Process finished with exit code 0
testInheritableThreadLocal()
Thread[main,5,main],a
Thread[Thread-0,5,main],a
Thread[Thread-0,5,main],b
Thread[main,5,main],a
Process finished with exit code 0
表述能力有限,说的不是很清楚,推荐博客(其它的真心没有看出诚意):
http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/