目录
1、StopWatch
1、1作用:
1、2方法:
1、3使用方法
2、ThreadLocal
2、1什么是ThreadLocal
2、2简单例子
2、3使用ThreadLocal带来的四个好处
2、4主要方法
2、5ThreadLocal内存泄漏问题
统计代码块耗时时间
引入依赖,Spring框架自带,可不引入
org.springframework
spring-core
${spring.version}
public class StopWatchTest {
// 用于模拟一些操作
private static void doSomeThing() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start("任务一");
doSomeThing();
stopWatch.stop();
stopWatch.start("任务二");
doSomeThing();
stopWatch.stop();
System.out.println("stopWatch.isRunning() = " + stopWatch.isRunning());
System.out.println(stopWatch.prettyPrint());
System.out.println("stopWatch.getTotalTimeMillis() = " + stopWatch.getTotalTimeMillis());
System.out.println("stopWatch.getLastTaskTimeMillis() = " + stopWatch.getLastTaskTimeMillis());
}
}
ThreadLocal
叫做本地线程变量,ThreadLocal
中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal
为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量.
同一个ThreadLocal所包含的对象,在不同的Threa中有不同的副本,这里有几点需要注意:
且该副本只能由当前Thread使用
,这也是ThreadLocal命名的由来。那就不存在多线程共享的问题
。ThreadLocal提供了线程本地的实例,它与普通变量的区别在于:
private static
修饰。ThreadLocal适用于每个线程需要自己独立的实例
,且该实例需要在多个方法中被使用
。即变量在线程间隔离,但是在方法和类间共享。
每个Thread
对象都有一个ThreadLocalMap
,每个ThreadLocalMap
可以存储多个ThreadLocal
2、2简单例子
如果没有ThreadLocal,定义一个全局变量后,所有线程都更改的是同一个值
public class TestThread1 {
//线程本地存储变量
public static int n =0;
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {//启动三个线程
Thread t = new Thread() {
@Override
public void run() {
add10ByThreadLocal();
}
};
t.start();
}
}
/**
* 线程本地存储变量加 5
*/
private static void add10ByThreadLocal() {
for (int i = 0; i < 5; i++) {
n += 1;
System.out.println(Thread.currentThread().getName() + " n=" + n);
}
}
}
最终 值为15,还可能出现线程安全问题
package com.ljx.splearn;
public class TestThreadLocal {
//线程本地存储变量
private static final ThreadLocal THREAD_LOCAL_NUM = new ThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {//启动三个线程
Thread t = new Thread() {
@Override
public void run() {
add10ByThreadLocal();
}
};
t.start();
}
}
/**
* 线程本地存储变量加 5
*/
private static void add10ByThreadLocal() {
for (int i = 0; i < 5; i++) {
Integer n = THREAD_LOCAL_NUM.get();
n += 1;
THREAD_LOCAL_NUM.set(n);
System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
}
}
}
打印结果:启动了 3 个线程,每个线程最后都打印到 "ThreadLocal num=5",而不是 num 一直在累加直到值等于 15
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
还有比如一个请求中需要访问三个服务,三个服务需要在子线程中依次修改用户信息,通常从session中取到用户信息,然后给三个线程操作,这样会出现线程安全问题,需要用锁来保证
使用ThreadLocal后,每个线程都是独立的,互不影响
SimpleDateFormat
,显然用ThreadLocal
可以节省内存和开销。ThreadLocal
使得代码耦合度更低,更优雅initialValue()
方法会返回当前线程对应的初始值
,这是一个延迟加载的方法,只有在调用get()
方法的时候才会触发。initialValue()
方法,这个方法会返回null
,一般使用匿名内部类的方法重写initialValue()
方法,以便在后续的使用中可以初始化副本对象。第一次调用get()方法
访问变量的时候,会调用initialValue()
方法,除非线程先前调用了set()
方法,在这种情况下,不会为线程调用本initialValue()
。initialValue()
方法,但如果已经调用一次remove()
方法后,再调用get()
方法,则可以再次调用initialValue()
,相当于第一次调用get()
。ThreadLocalMap
类是每个线程Thread
类里面的变量,但ThreadLocalMap
这个静态内部类定义在ThreadLocal
类中
ThreadLocalMap
中的每个Entry
都是一个对key
的弱引用
,同时,每个 Entry
都包含了一个对value
的强引用
正常情况下,当线程终止时,保存在ThreadLocalMap中的value也会被垃圾回收,因为没有任何强引用了,但是在项目中我们一般使用线程池,线程都是复用的,一般线程都不会结束,那么key对应的value就不会被回收
使用结束后及时调用remove()
方法,删除对应的Entry
对象,可以避免内存泄漏,所以使用完ThreadLocal
之后,应该调用remove()
方法。
可同步参考 ThreadLocal详解