从名称看,ThreadLocal 也就是thread和local的组合,也就是一个thread有一个local的变量副本,ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个自己独立的变量副本。
它与syncronized相反的思想,ThreadLocal 则从另一个角度来解决多线程的并发访问。 ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。 ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal 。
由于 ThreadLocal 中可以持有任何类型的对象,低版本 JDK 所提供的 get() 返回的是 Object 对象,需要强制类型转换。但 JDK 5.0 通过泛型很好的解决了这个问题,在一定程度地简化 ThreadLocal 的使用。 括起来说,对于多线程资源共享的问题,同步机制采用了 “ 以时间换空间 ” 的方式,而 ThreadLocal 采用了 “ 以空间换时间 ” 的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
package 线程池;
import java.util.Random;
import java.util.stream.IntStream;
/**
* @author Heian
* @time 19/03/18 11:51
* 用途:
*/
public class ThreadLocalTest {
private static ThreadLocal<Integer> local = new ThreadLocal<Integer>();
public static void main(String[] args) {
IntStream.range (0,3).forEach (value -> {
new Thread (() -> {
int num = new Random ().nextInt ();
local.set (num);
new A ().get (); new B ().get ();
},"线程" + String.valueOf (value)).start ();
});
}
static class A {
public void get() {
int data = local.get();
System.out.println("A from " + Thread.currentThread().getName()
+ " get data :" + data);
}
}
static class B {
public void get() {
int data = local.get();
System.out.println("B from " + Thread.currentThread().getName()
+ " get data :" + data);
}
}
}
运行结果:
A from 线程1 get data :-263818661
A from 线程2 get data :1880424954
A from 线程0 get data :-766329914
B from 线程0 get data :-766329914
B from 线程2 get data :1880424954
B from 线程1 get data :-263818661
定义了一个全局变量local,在多线程的并发修改值的前提下,容易发生数据错乱,但是把其封装到Threadlocal中,可以发现,每个存值和取值都是互不影响的,hreadLocal的设计理念就是共享变私有,都已经私有了,还谈啥共享,因为看起来就如同没有共享变量了,不共享即安全,但是他并不是为了解决线程安全问题而存在的。
实际应用:每次请求spring会创建一个线程,通过请求获取到用户的信息,存入ThreadLocal类中,在去执行其它操作每个线程访问的对象由于存在各自的局部变量中,所以互不影响。
private final static ThreadLocal<SystemUser> userThreadLocal = new ThreadLocal<SystemUser>();
@Autowired
private SystemLogService systemLogService;
/**
* 设置当前用户
* @author cary
* @param user
*/
public void setCurrentSystemUser(SystemUser user) {
userThreadLocal.set(user);
}
/**
* 从本地线程中取当前用户
* @author cary
* @return 系统用户
*/
public SystemUser getCurrentSystemUser() {
SystemUser user = userThreadLocal.get();
if (null == user) {
user = (SystemUser) SecurityUtils.getSubject().getPrincipal();
}
return user;
}
ThreadLocal最根本的使用场景应该是:在每个线程希望有一个独有的变量时(这个变量还很可能需要在同一个线程内共享)
ThreadLocal 解决SimpleDateformat 线程不安全问题
线程安全问题展示:
public class test {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
int num = i;
executorService.execute(() -> {
String s = DateFormatStr(num);
System.out.println(s);
});
}
executorService.shutdown();
}
public static String DateFormatStr(int second){
String format = sdf.format(1000*second);
return format;
}
}
截取部分打印信息
1970-01-01 08:00:05
1970-01-01 08:00:05
1970-01-01 08:00:06
1970-01-01 08:00:05
1970-01-01 08:00:05
.......
为了方便我们观察我们用future改造下
public class Task {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public static void main(String[] args) {
List<Future<String>> list = new ArrayList<>();
for (int i = 0; i < 50; i++) {
int num = i;
Future<String> future = (Future<String>) executorService.submit(() -> {
String s = DateFormatStr(num);
});
list.add(future);
}
List<String> result = new ArrayList<>();
list.forEach(task -> {
while (true){
if (task.isDone()){
try {
result.add(task.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}finally {
break;
}
}
}
});
executorService.shutdown();
List<String> collect = result.stream().sorted().collect(Collectors.toList());//报空指针
System.out.println(collect);
}
public static String DateFormatStr(int second){
String format = sdf.format(1000*second);
return format;
}
}
因为在线程池执行异步执行,result.stream() 每个element为null,sorted()方法要求每个element不为null,所以报错。需要稍微调整下
public class Task {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public static void main(String[] args) throws InterruptedException {
List<Callable<String>> listCall = new ArrayList<>();
//组装任务
for (int i = 0; i < 50; i++) {
int num = i;
listCall.add(() -> {
return DateFormatStr(num);
});
}
List<Future<String>> futures = executorService.invokeAll(listCall);
List<String> result = new ArrayList<>();
futures.forEach(task -> {
while (true){
if (task.isDone()){
try {
result.add(task.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}finally {
break;
}
}
}
});
executorService.shutdown();
List<String> collect = result.stream().sorted().collect(Collectors.toList());
for (String s : collect) {
System.out.println(s);
}
}
public static String DateFormatStr(int second){
String format = sdf.format(1000*second);
return format;
}
}
1970-01-01 08:00:01,
1970-01-01 08:00:04,
1970-01-01 08:00:04,
.......
显然此集合是存在重复数据的,当然此处是可以用锁的方式去解决线程安全问题,只需要在DateFormatStr加上syncronized关键字即可
public static String DateFormatStr(int second){
synchronized (sdf.getClass()){
String format = sdf.format(1000*second);
return format;
}
}
1970-01-01 08:00:00
1970-01-01 08:00:01
1970-01-01 08:00:02
.....
1970-01-01 08:00:48
1970-01-01 08:00:49
那通过ThreadLocal怎么解决呢?也很简单,只要在每个线程中存储属于自己的一份变量即可,说白了就是以空间交换了时间
public class Task3 {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
List<Callable<String>> listCall = new ArrayList<>();
//组装任务
for (int i = 0; i < 50; i++) {
int num = i;
listCall.add(() -> {
return DateFormatStr(num);
});
}
List<Future<String>> futures = executorService.invokeAll(listCall);
List<String> result = new ArrayList<>();
futures.forEach(task -> {
while (true){
if (task.isDone()){
try {
result.add(task.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}finally {
break;
}
}
}
});
executorService.shutdown();
List<String> collect = result.stream().sorted().collect(Collectors.toList());
for (String s : collect) {
System.out.println(s);
}
}
public static String DateFormatStr(int second){
local.set(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
String format = local.get().format(1000*second);
return format;
}
}
1970-01-01 08:00:00
1970-01-01 08:00:01
1970-01-01 08:00:02
.....
1970-01-01 08:00:48
1970-01-01 08:00:49
ThreadLocal 解决变量共享
private final static ThreadLocal<SystemUser> userThreadLocal = new ThreadLocal<SystemUser>();
@Autowired
private SystemLogService systemLogService;
/**
* 设置当前用户
*
* @author cary
* @param user
*/
public void setCurrentSystemUser(SystemUser user) {
userThreadLocal.set(user);
}
/**
* 从本地线程中取当前用户
*
* @author cary
* @return 系统用户
*/
public SystemUser getCurrentSystemUser() {
SystemUser user = userThreadLocal.get();
if (null == user) {
user = (SystemUser) SecurityUtils.getSubject().getPrincipal();
}
return user;
}
避免每个线程还需要主动地去创建这个对象(如果还需要共享,也一并解决了参数来回传递的问题)换句话说就是,“如何优雅的解决:线程间隔离与线程内共享的问题”,而不是说用来解决乱七八糟的线程安全问题,所以说如果有些场景你需要线程隔离,那么考虑ThreadLocal,而不是你有了什么线程安全问题需要解决,然后求助于ThreadLocal,这不是一回事。