目录
1、StopWatch
1、1作用:
1、2方法:
1、3使用方法
2、ThreadLocal
2、1什么是ThreadLocal
2、2简单例子
2、3使用ThreadLocal带来的四个好处
2、4主要方法
2、5ThreadLocal内存泄漏问题
1、StopWatch
1、1作用:
统计代码块耗时时间
1、2方法:
- 构造器:可以使用无参数构造器,也可使用传入一个String类型的id,创建一个指定了id的StopWatch
- start():可以不传入参数,开始一个无名称的任务的计时,也可以传入String类型的参数来开始指定任务名的任务计时
- stop():停止当前任务的计时
- isRunning():返回此stopWatch是否正在计时某任务
- getTotalTimeMillis():返回所有任务的总体执行时间(毫秒单位)
- getLastTaskTimeMillis():返回上一个任务的耗时(毫秒单位)
- prettyPrint():优美地打印所有任务的详细耗时情况
1、3使用方法
引入依赖,Spring框架自带,可不引入
<!-- spring核心包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
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());
}
}
2、ThreadLocal
2、1什么是ThreadLocal
ThreadLocal
叫做本地线程变量,ThreadLocal
中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal
为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量.
同一个ThreadLocal所包含的对象,在不同的Threa中有不同的副本,这里有几点需要注意:
- 因为每个Thread内有自己的实例副本,
且该副本只能由当前Thread使用
,这也是ThreadLocal命名的由来。 - 既然每个Thread都有自己的实例副本,且其他Thread不可访问,
那就不存在多线程共享的问题
。
ThreadLocal提供了线程本地的实例,它与普通变量的区别在于:
- 每个使用该变量的线程都会初始化一个完全独立的实例副本。
- ThreadLocal变量通常被
private static
修饰。 - 当一个线程结束时,它所使用的所有ThreadLocal相对的实例副本都可被回收。
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<Integer> THREAD_LOCAL_NUM = new ThreadLocal<Integer>() {
@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后,每个线程都是独立的,互不影响
2、3使用ThreadLocal带来的四个好处
- 不需要加锁,提高执行效率
- 线程安全
- 更高效地利用内存节省开销,上面例子中,相比于成千上万个任务,每个任务都新建一个
SimpleDateFormat
,显然用ThreadLocal
可以节省内存和开销。 - 免去传参的繁琐,不需要每次都传同样的参数,
ThreadLocal
使得代码耦合度更低,更优雅
2、4主要方法
initialValue()
方法会返回当前线程对应的初始值
,这是一个延迟加载的方法,只有在调用get()
方法的时候才会触发。- 如果不重写
initialValue()
方法,这个方法会返回null
,一般使用匿名内部类的方法重写initialValue()
方法,以便在后续的使用中可以初始化副本对象。 - 当线程
第一次调用get()方法
访问变量的时候,会调用initialValue()
方法,除非线程先前调用了set()
方法,在这种情况下,不会为线程调用本initialValue()
。 - 通常,每个线程最多调用一次
initialValue()
方法,但如果已经调用一次remove()
方法后,再调用get()
方法,则可以再次调用initialValue()
,相当于第一次调用get()
。
ThreadLocalMap
类是每个线程Thread
类里面的变量,但ThreadLocalMap
这个静态内部类定义在ThreadLocal
类中
2、5ThreadLocal内存泄漏问题
ThreadLocalMap
中的每个Entry
都是一个对key
的弱引用
,同时,每个 Entry
都包含了一个对value
的强引用
正常情况下,当线程终止时,保存在ThreadLocalMap中的value也会被垃圾回收,因为没有任何强引用了,但是在项目中我们一般使用线程池,线程都是复用的,一般线程都不会结束,那么key对应的value就不会被回收
使用结束后及时调用remove()
方法,删除对应的Entry
对象,可以避免内存泄漏,所以使用完ThreadLocal
之后,应该调用remove()
方法。
可同步参考 ThreadLocal详解