多线程编程(三)--Thread常用方法以及ThreadLocal

一、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),有三种情况会停止:

  1. 该位置的Entry为空,那么就用key和value新建一个Entry赋值到这个位置
  2. 到该位置的ThreadLocal为null,替换这个废弃的Entry。(一般情况就是由于弱引用被垃圾回收机制回收了)
  3. 到该位置的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的回收;


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值