第一种应用场景主要解决的是,工具类线程不安全,需要去解决工具类线程不安全的问题。
第二种应用场景主要解决的是,参数传递麻烦的问题。
下面进行的是SimpleDateFormat进行之路
package threadlocalpratice;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThreadLocalNormalUsage00 {
public String data(int second){
//参数的单位是毫秒,从1970-01-01 00:00:00开始
Date date = new Date(1000 * second);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
return simpleDateFormat.format(date);
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
String data = new ThreadLocalNormalUsage00().data(100000);
System.out.println(data);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
String data = new ThreadLocalNormalUsage00().data(100);
System.out.println(data);
}
}).start();
}
}
这是两个线程用SimpleDateFormat
package threadlocalpratice;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 1000个打印日期的任务,用线程池来执行
*/
public class ThreadLocalNormalUsage01 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
public String data(int second){
//参数的单位是毫秒,从1970-01-01 00:00:00开始
Date date = new Date(1000 * second);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
return simpleDateFormat.format(date);
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
int finalI = i;
//将任务提交到线程池中
threadPool.submit(new Runnable() {
@Override
public void run() {
String data = new ThreadLocalNormalUsage01().data(1000 + finalI);
System.out.println(data);
}
});
}
threadPool.shutdown();
}
}
这是1000个线程,使用线程池的方法来提高效率。这里的话发现SimpleDateFormat工具类,在每个线程中都被创建出来,浪费资源。
package threadlocalpratice;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 1000个打印日期的任务,用线程池来执行
* 由于1000个线程共用一个simpleDateFormat对象,会浪费,
* 所以把它写在类对象上。让1000个线程共用一个SimpleDateFormat对象
* 发现共用一个SimpleDateFormat会导致线程不安全
*/
public class ThreadLocalNormalUsage02 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public String data(int second){
//参数的单位是毫秒,从1970-01-01 00:00:00开始
Date date = new Date(1000 * second);
return simpleDateFormat.format(date);
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
int finalI = i;
//将任务提交到线程池中
threadPool.submit(new Runnable() {
@Override
public void run() {
String data = new ThreadLocalNormalUsage02().data(1000 + finalI);
System.out.println(data);
}
});
}
threadPool.shutdown();
}
}
这里让1000个线程都共用一个SimpleDateFormat对象。
package threadlocalpratice;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 1000个打印日期的任务,用线程池来执行
* 用加锁的方法来保证线程安全
*/
public class ThreadLocalNormalUsage03 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public String data(int second) {
//参数的单位是毫秒,从1970-01-01 00:00:00开始
Date date = new Date(1000 * second);
String s = null;
//选取simpleDateFormat来作为同步监控器
synchronized (simpleDateFormat) {
s = simpleDateFormat.format(date);
}
return s;
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
int finalI = i;
//将任务提交到线程池中
threadPool.submit(new Runnable() {
@Override
public void run() {
String data = new ThreadLocalNormalUsage03().data(1000 + finalI);
System.out.println(data);
}
});
}
threadPool.shutdown();
}
}
上面代码用加锁的方法来解决线程不安全的问题。但是,发现加锁的时候,效率比较低了。
给每一个线程都添加ThreadLocal 就是复制一份副本。
package threadlocalpratice;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 1000个打印日期的任务,用线程池来执行
* 由于1000个线程共用一个simpleDateFormat对象,会浪费,
* 使用ThreadLocal方法,由于只有10个线程,所以,其实只要同一个simpleDateFormat对象
* 复制9份就可以了
*
*/
public class ThreadLocalNormalUsage05 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
ThreadLocal<SimpleDateFormat> simpleDateFormat = new ThreadLocal(){
@Override
protected SimpleDateFormat initialValue(){
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
}
};
public String data(int second){
//参数的单位是毫秒,从1970-01-01 00:00:00开始
Date date = new Date(1000 * second);
return simpleDateFormat.get().format(date);
}
public static void main(String[] args) throws InterruptedException {
HashSet set = new HashSet();
for (int i = 0; i < 1000; i++) {
int finalI = i;
//将任务提交到线程池中
threadPool.submit(new Runnable() {
@Override
public void run() {
String data = new ThreadLocalNormalUsage05().data(1000 + finalI);
System.out.println(data);
set.add(data);
}
});
}
threadPool.shutdown();
Thread.sleep(2000);
System.out.println(threadPool.isTerminated());
System.out.println(set.size());
}
}
ThreadLocal 用途二:
传参的过程过于麻烦。不合适。
目标:每个线程内需要保存全局变量,可以让不同方法直接使用,避免参数传递的麻烦。
假设用UserMap
但是,这个方法也有问题:
当多线程同时工作时,我们需要保证线程安全,可以用synchronized,也可以用ConcurrentHashMap,但是,无论使用什么,都会对性能有所影响。
用ThreadLocal 不会影响性能
package threadlocalpratice;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 演示ThreadLocal的应用场景2,进行数据的传输
*/
public class ThreadLocalUsage06 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
int finalI = i;
threadPool.submit(new Runnable() {
@Override
public void run() {
new Service1( " " + finalI).process();
}
});
}
threadPool.shutdown();
}
}
class Service1 {
User user;
public Service1(String name){
user = new User(name);
}
public void process() {
//对对象中的数据进行设置
UserContextHolder.holder.set(user);
new Service2().process();
new Service3().process();
}
}
class Service2{
public void process(){
//这时候就可以得到User了
User user = UserContextHolder.holder.get();
System.out.println( user.name +" Service2拿到的user " + user.name);
}
}
class Service3{
public void process(){
//这时候就可以得到User了
User user = UserContextHolder.holder.get();
System.out.println( user.name + " Service3拿到的user " + user.name);
}
}
class UserContextHolder {
//使用方式2时,不需要进行初始化
public static ThreadLocal<User> holder = new ThreadLocal<>();
}
class User {
String name;
public User(String name) {
this.name = name;
}
}
总结
ThreadLocal的好处:
源码分析:
每一个Thread线程中,都会有一个ThreadLocalMap类, 里面会存储很多ThreadLocal 为什么需要map因为一个线程中可能会有多个ThreadLocal。
ThreadLocal:方法
下面在源码中进行查看,再没重写之前默认返回为null,重写后,调用get方法时就会执行该方法。
如果第一次调用get方法时,就会调用InitialValue。如果,之前调用了set方法,那么就不会去调用initialValue方法了。
原因如下:
由于map中不为null 因为你给它设置了值,所以,它就不会执行setInitialValue方法了。而拿到的result是set设置进行的。
分析get方法:
t代表当前线程:
getMap返回的是当前线程的threadLocals,这个threadLocals代表的是每个线程都有的ThreadLocalMap
如果map为空,就是没有set过 那么就是执行setInitialValue方法,这个方法代表的就是初始化InitialValue。如果map不为空(已经初始化过或者set过)。注意,这个map以及map中的key和value都是保存在线程中的,而不是保存在ThreadLocal中。
如果不为null 就以当前的ThreadLocal作为key(this)去找value,然后来进行返回。
下面分析的是set方法
相比之下,set就简单得多了:如果map不为空,那么就直接加入。如果为空,就新建,新建如下:
remove:方法
这里先获取ThreadLocal中的ThreadLocalMap 如果这个map不为空,就进行删除,但是,它不会把所有的都删除,它会把删除this,这个this代表的是调用它的ThreadLocal这个key所代表的key-value键值对。只能删除一个。
ThreadLocalMap类是Thread里面的一个成员变量。threadLocals ,类型是ThreadLocalMap
其有一个属性是:
这个可以认为是一个map,键值对:
键:ThreadLocal
值:实际需要的成员变量,比如uesr或者simpleDateFormat对象
但是,它和map有一点不同,hashmap碰到冲突会用链表的方法,或者红黑树,而它是进行找空的值,而进行填入。
总结一下
在每一个Thread中,都是会有一个ThreadLocalMap,里面会有一个个键值对。存入的key是ThreadLocal 类型,值的话,所有类型都可以。但调用get时,如果在ThreadLocalMap中没有,就会根据初始化函数进行创建。
ThreadLocal注意点
1.内存泄漏
这个key ThreadLocal代表的是弱引用。
而 value = v 代表的就是强引用;
但是,
因为value和Thread之间是强引用。所以,比如说我创建了十个对象,可以已经不用了,但是,还在内存中。
就是说,随着时间的增加,弱引用会被垃圾回收栈回收,但是,强引用会一直都在。这个value会一直在内存中。
在线程关闭之前要进行线程回收。不在使用的时候,主动调用remove()方法
ThreadLocal 碰到的空指针问题
package threadlocalpratice;
public class ThreadLocalNPE {
ThreadLocal longThreadLocal = new ThreadLocal<>();
public void set(){
longThreadLocal.set(Thread.currentThread().getId());
}
public long get(){
return longThreadLocal.get();
}
public static void main(String[] args) {
ThreadLocalNPE threadLocalNPE = new ThreadLocalNPE();
//这行语句会导致空指针异常,理论上,ThreadLocalNPE.longThreadLocal.get返回的是null。不会返回空指针异常的
System.out.println(threadLocalNPE.get());
Thread thread = new Thread(
new Runnable() {
@Override
public void run() {
threadLocalNPE.set();
long l = threadLocalNPE.get();
System.out.println(l);
}
}
);
//子线程的id
thread.start();
}
}
这里会导致空指针异常:
原因如下:
longThreadLocal.get()返回的是null,是Long类型的,但是想给他转化为long类型,所以,才导致了空指针类型报错。解决措施:只要把long改成Long即可,就不会有装箱拆箱问题了。
注意点: