案例
public class Demo_ThreadLocal {
private ThreadLocal<Integer> count = new ThreadLocal<Integer>() {//注意这里一定要指定泛型类型,此语法是匿名子类
@Override
protected Integer initialValue() {
return new Integer(0);
}
};
public int getNext() {
Integer value = count.get();
value++;
count.set(value);
return value;
}
public static void main(String[] args) {
Demo_ThreadLocal d = new Demo_ThreadLocal();
/**
* 线程1创建以及启动
*/
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("我是线程1" + Thread.currentThread().getName() + " " + d.getNext());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
/**
* 线程2创建以及启动
*/
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("我是线程2" + Thread.currentThread().getName() + " " + d.getNext());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
/**
* 线程3创建以及启动
*/
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("我是线程3" + Thread.currentThread().getName() + " " +
" " + d.getNext());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
控制台的输出(由于每个线程的执行任务都是用while(true)来修饰的,所以只能也只需截取一部分):
我是线程1Thread-0 1
我是线程2Thread-1 1
我是线程3Thread-2 1
我是线程1Thread-0 2
我是线程2Thread-1 2
我是线程1Thread-0 3
我是线程1Thread-0 4
我是线程3Thread-2 2
我是线程2Thread-1 3
我是线程1Thread-0 5
分析:
- 将每个线程分别看,每个线程都最后的计数都是从0开始:0、1、2、3逐步递增的,由此可见对于不同线程而言,各线程对应的ThreadLocal对象调用出的值相互独立,都是互不影响地从0,1,2如此递增的;
- 三个不同线程的sleep的输入参数不一样,线程1(对应Thread-0)为1s,线程2为2s,线程为3s;因此三个线程对应的ThreadLocal类对象中的值虽然是在递增,但是速度不一样,最快的线程1已经为5,线程3才为2;
ThreadLocal类的注意事项
- ThreadLocal类并不是Thread类的一个实例对象,“线程的局部变量”功能的实现是根据不同的线程,返回不同的数据引用,且各个数据引用之间相互独立。
- 其能够实现“线程的局部变量”功能的原因是,调用ThreadLocal类对象是在线程的run方法中进行的,而得到相关值又是调用了以下所示的代码。
public T get() { Thread t = Thread.currentThread();.......
- 从内存角度看,value值是属于ThreadLocal对象的,而不是属于Thread线程的。前者更像一个HashMap一样,key为Thread对象,value为线程局部变量,这一点是可以通过查看ThreadLocal的实现代码得到的。
- 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal类/synchronized关键字的对比:
- 在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
- ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
- 创建一个threadLocal类的对象时,就需要确定其返回的泛型类型。并且在主线程(mian方法)中创建一个threadLocal类对象时,无需指定其将存储多少个Thread类对象所对应的值,因为其是自动扩容的(类似于StringBuilder)。
- 线程的run方法中调用ThreadLocal类对象所存的值直接使用“ThreadLocal类对象.get方法”,而无需入口参数处设置当前的线程ID或者名字。
变量的作用域角度辨别ThreadLoal、局部变量、全局变量
Java是一门面向对象的语言,所以变量要么存在于类中,要么存在于方法中。
- 全局变量的作用域:存在于类中的变量,都被称作去全局变量。其又可以分为静态变量,可以被所有类所定义的实例对象访问,也可以直接
类名.变量
访问。非静态变量则属于类的实例,实例之间变量是相互独立的。 - 局部变量的作用域:方法以及代码块中所定义的变量都是局部变量,出了方法或者代码块,局部变量将无法被识别出来。
- ThreadLocal类实例中变量的作用域:假如是一个普通的
Integer
类型对象,其是同一个对象被多个线程访问。但是ThreadLocal类的作用域可以看作是一个线程一个作用域,线程中方法越多,ThreadLocal类在此线程中的作用域也就越广。通过ThreadLocal类内部Map结构的实现,变量的作用域做到了不再是对象,而是变得更窄,更特殊化的线程。
所以,全局变量作用域是类或者对象,局部变量的作用域是方法,而ThreadLocal类中的变量以及引用变量的作用域则是某一个线程。