一、Thread常用方法
在多线程编程中,thread类是很常用的。接下来就来学习一些常用的方法。
1)获取线程对象的名称和设置线程对象的名称
public final String getName():获取线程的名称。
public final void setName(String name):设置线程的名称
* 针对不是Thread类的子类中如何获取线程对象名称呢?
* public static Thread currentThread():返回当前正在执行的线程对象
* Thread.currentThread().getName()
package cn.itcast_03;
/*
* 该类要重写run()方法,为什么呢?
* 不是类中的所有代码都需要被线程执行的。
* 而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。
*/
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name){
super(name);
}
@Override
public void run() {
// 自己写代码
// System.out.println("好好学习,天天向上");
// 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进
for (int x = 0; x < 10; x++) {
System.out.println(Thread.currentThread().getName()+"=="+getName() + ":" + x);
}
}
}
测试:
package cn.itcast_03;
/*
* 如何获取线程对象的名称呢?
* public final String getName():获取线程的名称。
* 如何设置线程对象的名称呢?
* public final void setName(String name):设置线程的名称
*
* 针对不是Thread类的子类中如何获取线程对象名称呢?
* public static Thread currentThread():返回当前正在执行的线程对象
* Thread.currentThread().getName()
*/
public class MyThreadDemo {
public static void main(String[] args) {
// 创建线程对象
//无参构造+setXxx()
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//调用方法设置名称
my1.setName("林青霞");
my2.setName("刘意");
my1.start();
my2.start();
//带参构造方法给线程起名字
// MyThread my1 = new MyThread("林青霞");
// MyThread my2 = new MyThread("刘意");
// my1.start();
// my2.start();
//
//我要获取main方法所在的线程对象的名称,该怎么办呢?
//遇到这种情况,Thread类提供了一个很好玩的方法:
//public static Thread currentThread():返回当前正在执行的线程对象
System.out.println(Thread.currentThread().getName());
}
}
/*
名称为什么是:Thread-? 编号
class Thread {
private char name[];
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
//大部分代码被省略了
this.name = name.toCharArray();
}
public final void setName(String name) {
this.name = name.toCharArray();
}
private static int threadInitNumber; //0,1,2
private static synchronized int nextThreadNum() {
return threadInitNumber++; //return 0,1
}
public final String getName() {
return String.valueOf(name);
}
}
class MyThread extends Thread {
public MyThread() {
super();
}
}
*/
main
轩辕==轩辕:0
轩辕==轩辕:1
花狐貂==花狐貂:0
花狐貂==花狐貂:1
花狐貂==花狐貂:2
花狐貂==花狐貂:3
花狐貂==花狐貂:4
花狐貂==花狐貂:5
花狐貂==花狐貂:6
花狐貂==花狐貂:7
花狐貂==花狐貂:8
花狐貂==花狐貂:9
轩辕==轩辕:2
轩辕==轩辕:3
轩辕==轩辕:4
轩辕==轩辕:5
轩辕==轩辕:6
轩辕==轩辕:7
轩辕==轩辕:8
轩辕==轩辕:9
注意:在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。
2)获取线程对象的优先级和设置线程对象的优先级
注意:在我们的计算机只有一个cpu,那么cpu在某一时刻只能执行一条指令,线程只有得到cpu时间片,也就是使用权,才可以执行指令,那么Java是如何对线程进行调度的呢。
线程调度有两种模型分时调度和抢占式调度。
分时调度模型:所有线程轮流执行cpu的使用权,平均分配每个线程占用cpu的时间片。
抢占式调度模型:优先让优先级高的线程使用cpu的执行权,如果线程优先级相同,那么随机选择一个。优先级高的线程获取CPU时间片相对多一些。
public final int getPriority():返回线程对象的优先级
public final void setPriority(int newPriority):更改线程的优先级。
package cn.itcast_04;
public class ThreadPriority extends Thread {
@Override
public void run() {
for (int x = 0; x < 10; x++) {
System.out.println(getName() + ":" + x);
}
}
}
package cn.itcast_04;
/*
* 我们的线程没有设置优先级,肯定有默认优先级。
* 那么,默认优先级是多少呢?
* 如何获取线程对象的优先级?
* public final int getPriority():返回线程对象的优先级
* 如何设置线程对象的优先级呢?
* public final void setPriority(int newPriority):更改线程的优先级。
*
* 注意:
* 线程默认优先级是5。
* 线程优先级的范围是:1-10。
* 线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
*
* IllegalArgumentException:非法参数异常。
* 抛出的异常表明向方法传递了一个不合法或不正确的参数。
*
*/
public class ThreadPriorityDemo {
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();
tp1.setName("东方不败");
tp2.setName("岳不群");
tp3.setName("林平之");
// 获取默认优先级--
// System.out.println(tp1.getPriority());
// System.out.println(tp2.getPriority());
// System.out.println(tp3.getPriority());
// 设置线程优先级--线程优先级的范围是:1-10。IllegalArgumentException:非法参数异常。抛出的异常表明向方法传递了一个不合法或不正确的参数
// tp1.setPriority(100000);
//设置正确的线程优先级
tp1.setPriority(10);
tp2.setPriority(1);
tp1.start();
tp2.start();
tp3.start();
}
}
运行结果:
东方不败:0
东方不败:1
东方不败:2
东方不败:3
东方不败:4
东方不败:5
东方不败:6
东方不败:7
东方不败:8
岳不群:0
东方不败:9
林平之:0
林平之:1
林平之:2
林平之:3
林平之:4
林平之:5
岳不群:1
林平之:6
岳不群:2
岳不群:3
林平之:7
岳不群:4
岳不群:5
岳不群:6
岳不群:7
岳不群:8
岳不群:9
林平之:8
林平之:9
3)线程控制之线程休眠
public static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠
public static void sleep(long millis, int nanos):在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)
package cn.itcast_04;
import java.util.Date;
public class ThreadSleep extends Thread {
@Override
public void run() {
for (int x = 0; x < 10; x++) {
System.out.println(getName() + ":" + x + ",日期:" + new Date());
// 睡眠
// 困了,我稍微休息1秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package cn.itcast_04;
/*
* 线程休眠
* public static void sleep(long millis)
*/
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep();
ThreadSleep ts2 = new ThreadSleep();
ThreadSleep ts3 = new ThreadSleep();
ts1.setName("林青霞");
ts2.setName("林志玲");
ts3.setName("林志颖");
ts1.start();
ts2.start();
ts3.start();
}
}
运行结果:
林志玲:0,日期:Mon Apr 16 12:44:01 CST 2018
林青霞:0,日期:Mon Apr 16 12:44:01 CST 2018
林志颖:0,日期:Mon Apr 16 12:44:01 CST 2018
林志玲:1,日期:Mon Apr 16 12:44:02 CST 2018
林志颖:1,日期:Mon Apr 16 12:44:02 CST 2018
林青霞:1,日期:Mon Apr 16 12:44:02 CST 2018
林志玲:2,日期:Mon Apr 16 12:44:03 CST 2018
林志颖:2,日期:Mon Apr 16 12:44:03 CST 2018
林青霞:2,日期:Mon Apr 16 12:44:03 CST 2018
林志玲:3,日期:Mon Apr 16 12:44:04 CST 2018
林青霞:3,日期:Mon Apr 16 12:44:04 CST 2018
林志颖:3,日期:Mon Apr 16 12:44:04 CST 2018
林志颖:4,日期:Mon Apr 16 12:44:05 CST 2018
林志玲:4,日期:Mon Apr 16 12:44:05 CST 2018
林青霞:4,日期:Mon Apr 16 12:44:05 CST 2018
林志颖:5,日期:Mon Apr 16 12:44:06 CST 2018
林青霞:5,日期:Mon Apr 16 12:44:06 CST 2018
林志玲:5,日期:Mon Apr 16 12:44:06 CST 2018
林志颖:6,日期:Mon Apr 16 12:44:07 CST 2018
林志玲:6,日期:Mon Apr 16 12:44:07 CST 2018
林青霞:6,日期:Mon Apr 16 12:44:07 CST 2018
林志颖:7,日期:Mon Apr 16 12:44:08 CST 2018
林青霞:7,日期:Mon Apr 16 12:44:08 CST 2018
林志玲:7,日期:Mon Apr 16 12:44:08 CST 2018
林志颖:8,日期:Mon Apr 16 12:44:09 CST 2018
林志玲:8,日期:Mon Apr 16 12:44:09 CST 2018
林青霞:8,日期:Mon Apr 16 12:44:09 CST 2018
林志颖:9,日期:Mon Apr 16 12:44:10 CST 2018
林志玲:9,日期:Mon Apr 16 12:44:10 CST 2018
林青霞:9,日期:Mon Apr 16 12:44:10 CST 2018
4)线程控制之线程加入
public final void join():等待该线程终止。
public final synchronized void join(long millis):等待该线程终止的时间最长为 millis 毫秒
public final synchronized void join(long millis, int nanos):等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒
join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
package cn.itcast_04;
public class ThreadJoin extends Thread {
@Override
public void run() {
for (int x = 0; x < 10; x++) {
System.out.println(getName() + ":" + x);
}
}
}
package cn.itcast_04;
/*
* public final void join():等待该线程终止。
*/
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();
tj1.setName("李渊");
tj2.setName("李世民");
tj3.setName("李元霸");
tj1.start();
try {
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
tj2.start();
tj3.start();
}
}
李渊:0
李渊:1
李渊:2
李渊:3
李渊:4
李渊:5
李渊:6
李渊:7
李渊:8
李渊:9
李世民:0
李世民:1
李世民:2
李元霸:0
李元霸:1
李元霸:2
李元霸:3
李元霸:4
李元霸:5
李元霸:6
李元霸:7
李元霸:8
李元霸:9
李世民:3
李世民:4
李世民:5
李世民:6
李世民:7
李世民:8
李世民:9
为什么要用join()方法
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
不加join()
package cn.itcast_04;
public class ThreadJoin_01 extends Thread {
private String name;
public ThreadJoin_01(String name){
super(name);
this.name=name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 线程运行开始!");
for(int i=0;i<5;i++){
System.out.println("循环中子线程"+name + "运行 : " + i);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 线程运行结束!");
}
}
package cn.itcast_04;
public class ThreadJoinDemo_01 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
ThreadJoin_01 tj1=new ThreadJoin_01("李渊");
ThreadJoin_01 tj2=new ThreadJoin_01("李元霸");
tj1.start();
tj2.start();
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
}
}
运行结果:
main主线程运行开始!
李渊 线程运行开始!
循环中子线程李渊运行 : 0
main主线程运行结束!
李元霸 线程运行开始!
循环中子线程李元霸运行 : 0
循环中子线程李渊运行 : 1
循环中子线程李渊运行 : 2
循环中子线程李元霸运行 : 1
循环中子线程李元霸运行 : 2
循环中子线程李元霸运行 : 3
循环中子线程李元霸运行 : 4
李元霸 线程运行结束!
循环中子线程李渊运行 : 3
循环中子线程李渊运行 : 4
李渊 线程运行结束!
发现主线程比子线程早结束。
加入join()方法
package cn.itcast_04;
public class ThreadJoinDemo_02 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
ThreadJoin_01 tj1=new ThreadJoin_01("李渊");
ThreadJoin_01 tj2=new ThreadJoin_01("李元霸");
tj1.start();
tj2.start();
try {
tj1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
tj2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
}
}
运行结果:
main主线程运行开始!
李元霸 线程运行开始!
循环中子线程李元霸运行 : 0
李渊 线程运行开始!
循环中子线程李渊运行 : 0
循环中子线程李元霸运行 : 1
循环中子线程李渊运行 : 1
循环中子线程李元霸运行 : 2
循环中子线程李渊运行 : 2
循环中子线程李渊运行 : 3
循环中子线程李渊运行 : 4
循环中子线程李元霸运行 : 3
李渊 线程运行结束!
循环中子线程李元霸运行 : 4
李元霸 线程运行结束!
main主线程运行结束!
主线程一定会等子线程都结束了才结束
5)线程控制之线程礼让==让多个线程的执行更和谐,但是不能靠它保证一人一次
public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
package cn.itcast_04;
public class ThreadYield extends Thread {
@Override
public void run() {
for (int x = 0; x < 20; x++) {
System.out.println(getName() + ":" + x);
// 当i为10时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if(x==10){
Thread.yield();
}
}
}
}
测试类:
package cn.itcast_04;
/*
* public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
* 让多个线程的执行更和谐,但是不能靠它保证一人一次。
*/
public class ThreadYieldDemo {
public static void main(String[] args) {
ThreadYield ty1 = new ThreadYield();
ThreadYield ty2 = new ThreadYield();
ty1.setName("轩辕");
ty2.setName("花狐貂");
ty1.start();
ty2.start();
}
}
运行结果(每次结果可能不一样):
第一种情况:轩辕(线程)当执行到10时会CPU时间让掉,这时花狐貂(线程)抢到CPU时间并执行
第二种情况:花狐貂(线程)当执行到10时会CPU时间让掉,这时轩辕(线程)抢到CPU时间并执行。
花狐貂:0
花狐貂:1
花狐貂:2
花狐貂:3
花狐貂:4
花狐貂:5
花狐貂:6
花狐貂:7
花狐貂:8
花狐貂:9
花狐貂:10
轩辕:0
轩辕:1
轩辕:2
轩辕:3
轩辕:4
轩辕:5
轩辕:6
轩辕:7
轩辕:8
轩辕:9
轩辕:10
轩辕:11
轩辕:12
花狐貂:11
花狐貂:12
花狐貂:13
轩辕:13
轩辕:14
轩辕:15
轩辕:16
轩辕:17
轩辕:18
轩辕:19
花狐貂:14
花狐貂:15
花狐貂:16
花狐貂:17
花狐貂:18
花狐貂:19
总结:
sleep()和yield()的区别:sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
6)线程控制之守护线程
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
package cn.itcast_04;
public class ThreadDaemon extends Thread {
@Override
public void run() {
for (int x = 0; x < 10; x++) {
System.out.println(getName() + ":" + x);
}
}
}
测试类:
package cn.itcast_04;
/*
* public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
* 当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
*
* 游戏:坦克大战。
*/
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("关羽");
td2.setName("张飞");
// 设置收获线程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
Thread.currentThread().setName("刘备");
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
7)线程控制之线程中断
public final void stop():让线程停止,过时了,但是还可以使用。
public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。
-
如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的
checkAccess
方法就会被调用,这可能抛出SecurityException
。如果线程在调用
Object
类的wait()
、wait(long)
或wait(long, int)
方法,或者该类的join()
、join(long)
、join(long, int)
、sleep(long)
或sleep(long, int)
方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException
。如果该线程在
可中断的通道
上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个
ClosedByInterruptException
。如果该线程在一个
Selector
中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的wakeup
方法一样。如果以前的条件都没有保存,则该线程的中断状态将被设置。
中断一个不处于活动状态的线程不需要任何作用。
-
-
-
抛出:
-
SecurityException
- 如果当前线程无法修改该线程
-
package cn.itcast_04;
import java.util.Date;
public class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("开始执行:" + new Date());
// 我要休息10秒钟,亲,不要打扰我哦
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("线程被终止了");
}
System.out.println("结束执行:" + new Date());
}
}
测试类:
package cn.itcast_04;
/*
* public final void stop():让线程停止,过时了,但是还可以使用。
* public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。
*/
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
// 你超过三秒不醒过来,我就干死你
try {
Thread.sleep(3000);
// ts.stop();
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
开始执行:Mon Apr 16 16:02:02 CST 2018
线程被终止了
结束执行:Mon Apr 16 16:02:10 CST 2018
线程的消亡不能通过调用stop()命令,而是让run()方法自然结束。stop()方法是不安全的,已经废弃。
停止线程推荐的方式:设定一个标志变量,在run()方法中是一个循环,由该标志变量控制循环是继续执行还是跳出;循环跳出,则线程结束。
二、ThreadLocal
2.1 ThreadLocal的认识与理解
ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。很多地方叫做线程本地变量,也有些地方叫做线程本地存储。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal就比较好的解决了这个问题。
如:
class ConnectionManager {
private static Connection connect = null;
public static Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public static void closeConnection() {
if(connect!=null)
connect.close();
}
}
假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问题:第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。
所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。这样将会大大影响程序执行效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待。
分析一下这个问题,这地方到底需不需要将connect变量进行共享?事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。
既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。
class ConnectionManager {
private static Connection connect = null;
public static Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public static void closeConnection() {
if(connect!=null)
connect.close();
}
}
class ConnectionManager {
private Connection connect = null;
public Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public void closeConnection() {
if(connect!=null)
connect.close();
}
}
class Dao{
public void insert() {
ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.openConnection();
//使用connection进行操作
connectionManager.closeConnection();
}
}
这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。
虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
2.2 ThreadLocal类源码解读
package java.lang;
import java.lang.ref.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class ThreadLocal<T> {
/**
* ThreadLocal将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,
* ThreadLocal实例hash值,用来区分不同实例
*
*/
private final int threadLocalHashCode = nextHashCode();
/**
* 可以看作hash值的一个基值
*/
private static AtomicInteger nextHashCode =new AtomicInteger();
/**
*hash值每次增加量
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
*
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
* 初始化函数
* 此方法在每个线程中最多执行一次,如果第一次执行get(),会调用此方法
* 如果在第一次执行get()之前已经调用过set(),则此方法永远不执行
* 可以看到默认返回null值,为了避免不必要错误,最好重写此方法
*/
protected T initialValue() {
return null;
}
/**
* Creates a thread local variable. The initial value of the variable is
* determined by invoking the {@code get} method on the {@code Supplier}.
*
* @param <S> the type of the thread local's value
* @param supplier the supplier to be used to determine the initial value
* @return a new thread local variable
* @throws NullPointerException if the specified supplier is null
* @since 1.8
*/
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
/**
* 无参构造函数
*/
public ThreadLocal() {
}
/**
*
* 获取线程所属的值
*/
public T get() {
//得到当前线程
Thread t = Thread.currentThread();
//得到当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果map不为null
if (map != null) {
//得到map中Entry实体对象
ThreadLocalMap.Entry e = map.getEntry(this);
//如果e不为空,则取出Entry对象中的value值,然后返回
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//在没有map或map中没有添加该ThreadLocal时,则创建ThreadLocalMap对象,并且创建一个空的T对象放到map中,最后返回null
return setInitialValue();
}
/**
* 初始化.
*
* @return the initial value
*/
private T setInitialValue() {
//初始化函数,返回null
T value = initialValue();
//得到当前线程
Thread t = Thread.currentThread();
//得到当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//map不为空,则将value放到map
if (map != null)
map.set(this, value);
//否则如果Thread中并没有map,则新建一个,然后将value放到map中,这里注意,是每个Thread维护一个ThreadLocalMap
else
createMap(t, value);
return value;
}
/**
* 把value放到当前线程的ThreadLocalMap对象中去,其中key值与当前ThreadLocal对象的threadLocalHashCode值有关
*
*
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//这里同样有可能调用创建ThreadLocalMap
createMap(t, value);
}
/**
*
*
*
*删除当前线程的 ThreadLocalMap对象中 key为当前ThreadLocal 的Entry(包含key/value)
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
* 取得TheadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
*创建TheadLocalMap
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* Factory method to create map of inherited thread locals.
* Designed to be called only from Thread constructor.
*
* @param parentMap the map associated with parent thread
* @return a map containing the parent's inheritable bindings
*/
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
/**
* Method childValue is visibly defined in subclass
* InheritableThreadLocal, but is internally defined here for the
* sake of providing createInheritedMap factory method without
* needing to subclass the map class in InheritableThreadLocal.
* This technique is preferable to the alternative of embedding
* instanceof tests in methods.
*/
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
/**
* SuppliedThreadLocal是JDK8新增的内部类,只是扩展了ThreadLocal的初始化值的方法而已,允许使用JDK8新增的Lambda表达式赋值。
* 需要注意的是,函数式接口Supplier不允许为null
*/
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
/**
* 静态内部类ThreadLcoalMap 用来存储每个线程的局部变量
*/
static class ThreadLocalMap {
/**
* Entry继承自WeakReference类,是存储线程私有变量的数据结构
* ThreadLocal实例作为引用,意味着如果ThreadLocal实例为null
* 就可以从table中删除对应的Entry
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 把ThreadLocal与value封装成Entry*/
Object value;
/** 把ThreadLocal与value封装成Entry*/
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 数组初始容量为16
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 存储数组,数组的长度必须是2^
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* 数组的初始化大小
*/
private int size = 0;
/**
* 临界值加载因子默认为0
*/
private int threshold;
/**
* 设置 临界值setThreshold
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 从i递增到len
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* 从i递减到1的过程,返回i-1,当i<1返回len-1
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* 构造函数.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
/**
* Construct a new map including all Inheritable ThreadLocals
* from given parent map. Called only by createInheritedMap.
*
* @param parentMap the map associated with parent thread.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
/**
* 通过set方法往里面塞值
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 根据 ThreadLocal 的散列值,查找对应元素在数组中的位置
int i = key.threadLocalHashCode & (len-1);
// 使用线性探测法查找元素
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//ThreadLocal 对应的 key 存在,直接覆盖之前的值
if (k == key) {
e.value = value;
return;
}
//key为 null,但是值不为 null,说明之前的 ThreadLocal 对象已经被回收了,当前数组中的 Entry 是一个陈旧(stale)的元素
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
/**
* Replace a stale entry encountered during a set operation
* with an entry for the specified key. The value passed in
* the value parameter is stored in the entry, whether or not
* an entry already exists for the specified key.
*
* As a side effect, this method expunges all stale entries in the
* "run" containing the stale entry. (A run is a sequence of entries
* between two null slots.)
*
* @param key the key
* @param value the value to be associated with key
* @param staleSlot index of the first stale entry encountered while
* searching for key.
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
/**
* Expunge a stale entry by rehashing any possibly colliding entries
* lying between staleSlot and the next null slot. This also expunges
* any other stale entries encountered before the trailing null. See
* Knuth, Section 6.4
*
* @param staleSlot index of slot known to have null key
* @return the index of the next null slot after staleSlot
* (all between staleSlot and this slot will have been checked
* for expunging).
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
/**
* Heuristically scan some cells looking for stale entries.
* This is invoked when either a new element is added, or
* another stale one has been expunged. It performs a
* logarithmic number of scans, as a balance between no
* scanning (fast but retains garbage) and a number of scans
* proportional to number of elements, that would find all
* garbage but would cause some insertions to take O(n) time.
*
* @param i a position known NOT to hold a stale entry. The
* scan starts at the element after i.
*
* @param n scan control: {@code log2(n)} cells are scanned,
* unless a stale entry is found, in which case
* {@code log2(table.length)-1} additional cells are scanned.
* When called from insertions, this parameter is the number
* of elements, but when from replaceStaleEntry, it is the
* table length. (Note: all this could be changed to be either
* more or less aggressive by weighting n instead of just
* using straight log n. But this version is simple, fast, and
* seems to work well.)
*
* @return true if any stale entries have been removed.
*/
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
/**
* R当前的存储的对象个数是否已经超出了阈值(threshold的值)大小,如果超出了,需要重新扩充并将所有的对象重新计算位置(rehash函数来实现)
*/
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
/**
* Double the capacity of the table.
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
//ThreadLocalMap的初始长度为16,每次扩容都增长为原来的2倍,即它的长度始终是2的n次方
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
/**
* Expunge all stale entries in the table.
*/
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
}
}
Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。每个线程在往某个ThreadLocal里塞值的时候,都会往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
jdk文档里给出了四个普通方法和一个构造方法,我们依次来学习。
2.3.1 ThreadLocal构造方法
/**
* 无参构造函数
*/
public ThreadLocal() {
}
2.3.2 ThreadLocal普通方法-initialValue方法
/**
* 初始化函数
* 此方法在每个线程中最多执行一次,如果第一次执行get(),会调用此方法
* 如果在第一次执行get()之前已经调用过set(),则此方法永远不执行
* 可以看到默认返回null值,为了避免不必要错误,最好重写此方法
*/
protected T initialValue() {
return null;
}
2.3.4ThreadLocal普通方法--set(T value)方法
/**
* 把value放到当前线程的ThreadLocalMap对象中去,其中key值与当前ThreadLocal对象的threadLocalHashCode值有关
*
*
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//这里同样有可能调用创建ThreadLocalMap
createMap(t, value);
}
1)ThreadLocalMap----静态内部类ThreadLcoalMap 用来存储每个线程的局部变量
/**
* 静态内部类ThreadLcoalMap 用来存储每个线程的局部变量
*/
static class ThreadLocalMap {
/**
* Entry继承自WeakReference类,是存储线程私有变量的数据结构
* ThreadLocal实例作为引用,意味着如果ThreadLocal实例为null
* 就可以从table中删除对应的Entry
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
/** 把ThreadLocal与value封装成Entry*/
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 数组初始容量为16
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 存储数组,数组的长度必须是2^
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* 数组的初始化大小
*/
private int size = 0;
/**
* 默认为0
*/
private int threshold;
/**
* 设置setThreshold
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* 构造函数.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
/**
* Construct a new map including all Inheritable ThreadLocals
* from given parent map. Called only by createInheritedMap.
*
* @param parentMap the map associated with parent thread.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
// 根据 ThreadLocal 的散列值,查找对应元素在数组中的位置
int i = key.threadLocalHashCode & (len-1);
// 使用线性探测法查找元素
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//ThreadLocal 对应的 key 存在,直接覆盖之前的值
if (k == key) {
e.value = value;
return;
}
//key为 null,但是值不为 null,说明之前的 ThreadLocal 对象已经被回收了,当前数组中的 Entry 是一个陈旧(stale)的元素
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
/**
* Replace a stale entry encountered during a set operation
* with an entry for the specified key. The value passed in
* the value parameter is stored in the entry, whether or not
* an entry already exists for the specified key.
*
* As a side effect, this method expunges all stale entries in the
* "run" containing the stale entry. (A run is a sequence of entries
* between two null slots.)
*
* @param key the key
* @param value the value to be associated with key
* @param staleSlot index of the first stale entry encountered while
* searching for key.
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// Back up to check for prior stale entry in current run.
// We clean out whole runs at a time to avoid continual
// incremental rehashing due to garbage collector freeing
// up refs in bunches (i.e., whenever the collector runs).
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// Find either the key or trailing null slot of run, whichever
// occurs first
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// If we find key, then we need to swap it
// with the stale entry to maintain hash table order.
// The newly stale slot, or any other stale slot
// encountered above it, can then be sent to expungeStaleEntry
// to remove or rehash all of the other entries in run.
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// Start expunge at preceding stale entry if it exists
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// If we didn't find stale entry on backward scan, the
// first stale entry seen while scanning for key is the
// first still present in the run.
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// If there are any other stale entries in run, expunge them
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
/**
* Expunge a stale entry by rehashing any possibly colliding entries
* lying between staleSlot and the next null slot. This also expunges
* any other stale entries encountered before the trailing null. See
* Knuth, Section 6.4
*
* @param staleSlot index of slot known to have null key
* @return the index of the next null slot after staleSlot
* (all between staleSlot and this slot will have been checked
* for expunging).
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
/**
* Heuristically scan some cells looking for stale entries.
* This is invoked when either a new element is added, or
* another stale one has been expunged. It performs a
* logarithmic number of scans, as a balance between no
* scanning (fast but retains garbage) and a number of scans
* proportional to number of elements, that would find all
* garbage but would cause some insertions to take O(n) time.
*
* @param i a position known NOT to hold a stale entry. The
* scan starts at the element after i.
*
* @param n scan control: {@code log2(n)} cells are scanned,
* unless a stale entry is found, in which case
* {@code log2(table.length)-1} additional cells are scanned.
* When called from insertions, this parameter is the number
* of elements, but when from replaceStaleEntry, it is the
* table length. (Note: all this could be changed to be either
* more or less aggressive by weighting n instead of just
* using straight log n. But this version is simple, fast, and
* seems to work well.)
*
* @return true if any stale entries have been removed.
*/
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
/**
* Re-pack and/or re-size the table. First scan the entire
* table removing stale entries. If this doesn't sufficiently
* shrink the size of the table, double the table size.
*/
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
/**
* Double the capacity of the table.
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
//ThreadLocalMap的初始长度为16,每次扩容都增长为原来的2倍,即它的长度始终是2的n次方
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
/**
* Expunge all stale entries in the table.
*/
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
}
1.1) Entry对象与Entry数组
/**
* Entry继承自WeakReference类,是存储线程私有变量的数据结构
* ThreadLocal实例作为引用,意味着如果ThreadLocal实例为null
* 就可以从table中删除对应的Entry
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 把ThreadLocal与value封装成Entry*/
Object value;
/** 把ThreadLocal与value封装成Entry*/
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 存储数组,数组的长度必须是2^
* table.length MUST always be a power of two.
*/
private Entry[] table;
从源码可以看出ThreadLocalMap存储的是ThreadLocalMap.Entry对象。也就是说ThreadLocalMap的大部分方法实际上都是对Entry的操作。ThreadLocalMap维护一张哈希表(一个数组),里面存的是Entry对象。从hashMap的源码我们知道既然是哈希表,那肯定就会涉及到加载因子,即当表里面存储的对象达到容量的多少百分比的时候需要扩容。ThreadLocalMap中定义了threshold属性,当表里存储的对象数量超过threshold就会扩容。
/**
* 临界值加载因子默认为0
*/
private int threshold;
/**
* 设置 临界值setThreshold
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
如上源码加载因子设置为2/3。即每次容量超过设定的len的2/3时,需要扩容
1.2)ThreadLocalMap的初始化
/**
* 构造函数.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
/**
* Construct a new map including all Inheritable ThreadLocals
* from given parent map. Called only by createInheritedMap.
*
* @param parentMap the map associated with parent thread.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
它首先创建一个大小为16的实体数组,然后将threadLocalHashCode 和(length-1)进行与操作从而求得index,这个操作与threadLocalHashCode %length相等,之所以用与操作是因为更快。
获取到index之后,就把这第一个值存入数组。
至于设置的threshold ,其实就是代表着可用的length,如果超过了这个值,就会执行扩容操作。
1.3)set(ThreadLocal<?> key, Object value)方法
/**
* 通过set方法往里面塞值
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 根据 ThreadLocal 的散列值,查找对应元素在数组中的位置
int i = key.threadLocalHashCode & (len-1);
// 使用线性探测法查找元素
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//ThreadLocal 对应的 key 存在,直接覆盖之前的值
if (k == key) {
e.value = value;
return;
}
//key为 null,但是值不为 null,说明之前的 ThreadLocal 对象已经被回收了,当前数组中的 Entry 是一个陈旧(stale)的元素
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
/**
* 从i递增到len
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
这里有一个循环,index会一直执行加1操作(但是小于length),有三种情况会停止:
- 该位置的Entry为空,那么就用key和value新建一个Entry赋值到这个位置
- 到该位置的ThreadLocal为null,替换这个废弃的Entry。(一般情况就是由于弱引用被垃圾回收机制回收了)
- 到该位置的ThreadLocal等于我们要赋值的ThreadLocal,直接使用value覆盖那个值
从上面的代码看出通过key的hashCode来计算存储的索引位置i.如果i位置已经存储了对象,那么就往后挪一个位置依次类推,直到找到空的位置,再将对象存放。另外,在最后还需要判断一下当前的存储的对象个数是否已经超出了阈值(threshold的值)大小,如果超出了,需要重新扩充并将所有的对象重新计算位置(rehash函数来实现)。
rehash的原理是:int h = k.threadLocalHashCode & (len - 1); h!=i,表明当初在新增的时候,就发生了hash冲突,导致该entry在table的位置后移。处理方式是:将tab[i]=null,重新找index为h及之后的table,直道找到table[h]=null,然后将table[h] = e。
/**
* R当前的存储的对象个数是否已经超出了阈值(threshold的值)大小,如果超出了,需要重新扩充并将所有的对象重新计算位置(rehash函数来实现)
*/
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
rehash函数里面先调用了expungeStaleEntry方法,然后再判断当前存储对象的大小是否超出了阈值的3/4.ThreadLocalMap里面存储的Entry对象本质上是一个WeakReference<ThreadLocal<?>>.也就是说里面存储的对象本质是一个对ThreadLocal对象的弱引用,该ThreadLocal
随时可能会被回收!ThreadLocalMap里面对应的value的key是null.我们需要把这样的Entry对象清除掉。
resize()
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
//ThreadLocalMap的初始长度为16,每次扩容都增长为原来的2倍,即它的长度始终是2的n次方
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
将新的table大小设为旧的table大小的2倍。然后将旧的table中entry挨个插入到新的table中,当然,插入前还是要判断entry是否过期。
插入过程中,如果发现hash冲突(int h = k.threadLocalHashCode & (newLen - 1); ),则查找h之后的table元素,知道table[h] != null,将table[h] = e;
ThreadLocal之get流程
a)根据hashCode和数组长度计算元素放置的位置即数组下表
b)根据a得到的数组下标开始往后遍历,如果key相等则覆盖Value,如果key为null,则用新的key和value覆盖,同时清除历史key=null的数据
c)如果超过阈(yu\)值,就需要再哈希:
c1. 清理一遍陈旧数据
c2. >=3/4的阈值就需要执行扩容,就把table扩容为两倍
c3. 把老数据从新哈希进去新的table
2.3.5)ThreadLocal普通方法-get()方法
/**
*
* 获取线程所属的值
*/
public T get() {
//得到当前线程
Thread t = Thread.currentThread();
//得到当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果map不为null
if (map != null) {
//得到map中Entry实体对象
ThreadLocalMap.Entry e = map.getEntry(this);
//如果e不为空,则取出Entry对象中的value值,然后返回
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//在没有map或map中没有添加该ThreadLocal时,则创建ThreadLocalMap对象,并且创建一个空的T对象放到map中,最后返回null
return setInitialValue();
}
/**
* 取得TheadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
getMap(t)返回当前线程的成员变量ThreadLocalMap(Thread的成员变量有ThreadLocalMap,这一点可以查看Thread的源码,如下)很明确的说明了ThreadLocal属于线程,ThreadLocalMap由ThreadLocal持有,说到底,ThreadLocalMap 也是线程所持有。每个线程Thread都有自己的ThreadLocalMap。
没有设值,所以map 为空,直接回调setInitialValue(),若事先有设值,且map不为空,通过map.getEntry(this)将ThreadLocal对象作为key传入。
先通过 key.threadLocalHashCode & (table.length - 1) 运算得到Entry的索引 i,再通过table[i]得到Entry e进行判断,如果e不为空,则通过e.get()拿出entry里面的key和传入的key比较,若相等,直接返回e,否则回调getEntryAfterMiss(key, i, e)。
ThreadLocal之get流程
a)获取当前线程t;
b)返回当前线程t的成员变量ThreadLocalMap(以下简写map)
c)map不为null,则获取以当前线程为key的ThreadLocalMap的Entry(以下简写e),如果e不为null,则直接返回该Entry的value
d)如果map为null或者e为null,返回setInitialValue()的值。setInitialValue()调用重写的initialValue()返回新值(如果没有重写initialValue将返回默认值null),并将新值存入当前线程的ThreadLocalMap(如果当前线程没有ThreadLocalMap,会先创建一个)
2.3.5)ThreadLocal普通方法-remove()方法
/**
*
*
*
*删除当前线程的 ThreadLocalMap对象中 key为当前ThreadLocal 的Entry(包含key/value)
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
* 取得TheadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap的remove方法
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
remove()移除当前线程的当前ThreadLocal数据(只是清空该key-value键值对),而且是立即移除,移除后,再调用get方法将重新调用initialValue方法初始化(除非在此期间调用了set方法赋值)
总结:
1)一个线程中的所有的局部变量其实存储在该线程自己的同一个map属性中;
2)线程死亡时,线程局部变量会自动回收内存;
3)线程局部变量时通过一个 Entry 保存在map中,该Entry 的key是一个 WeakReference包装的ThreadLocal, value为线程局部变量;
key 到 value 的映射是通过:ThreadLocal.threadLocalHashCode & (INITIAL_CAPACITY - 1) 来完成的;
4)当线程拥有的局部变量超过了容量的2/3(没有扩大容量时是10个),会涉及到ThreadLocalMap中Entry的回收;