1.创建线程集中方式
两种
继承Thread
实现Runnable
两个工具辅助创建线程,控制线程的执行
线程池
Callable/Future
2.ThreadLocal
辅助一个线程持有自己的数据,这个数据与其他线程不共享
把数据绑定到线程,线程当做一条流水线,来传递数据,多线程并行时,数据是安全的
ThreadLocal<Double> threadLocal = new ThreadLocal<Double>();
// 在当前线程上绑定数据
threadLocal.set(2.744625);
// 从当前线程获取绑定的数据
threadLocal.get();
// 从当前线程删除数据
threadLocal.remove();
存储结构
线程中封装一个 Map
用ThreadLocal实例作为键
对应的值是绑定的数据
package day14;
public class TestThreadLocal {
static ThreadLocal<Double> threadLocal = new ThreadLocal<Double>();
public static void main(String[] args) {
new Thread() {
public void run() {
a();
b();
c();
clearData();
}
}.start();
new Thread() {
public void run() {
c();
a();
b();
clearData();
}
}.start();
}
static void a() {
String n = Thread.currentThread().getName();
Double d = data();
System.out.println(n+" - "+d);
}
static void b() {
String n = Thread.currentThread().getName();
Double d = data();
System.out.println(n+" - "+d);
}
static void c() {
String n = Thread.currentThread().getName();
Double d = data();
System.out.println(n+" - "+d);
}
static Double data() {
Double d = threadLocal.get();
if (d == null) {
d = Math.random();
threadLocal.set(d);
}
return d;
}
/**用完删除线程中存储的数据,重要,否则会内存泄漏*/
static void clearData() {
threadLocal.remove();
}
}
JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的解决思路。使用这个工具类可以很简洁地编写出优美的多线程程序。这里注意线程锁是解决并发安全问题,而ThreadLocal是解决线程共享问题,何为共享呢?
例如:我们要使用一个对象,这个对象要在不同线程中传递,怎么保持一份呢?不是新对象呢?就需要使用ThreadLocal来保存和传递这个对象。
3. 结构
可以看到ThreadLocal内部由ThreadLocalMap本质就是一个Map结构。其内部保存Entry对象,Entry的key是弱引用(用完就释放),value是强引用(GC回收,如果有引用回收不了)。ThreadLocal被保存在各自的线程Thread中,线程底层由操作系统保证其隔离。相当于共享变量再每个线程中复制了一份。这样共享变量就不会有访问冲突了。其本质是线程私有了。当然不会造成冲突,从而间接的解决了线程安全问题。实际开发,特别框架中广泛用到。
4.副本
数据保存在ThreadLocalMap集合中,这样数据就被每个线程所绑定,操作系统会维护线程的隔离,也就是不能互相访问,从而巧妙的避免线程安全问题,而它所消耗的资源几乎没有,多线程下也不会发生阻塞,性能非常好,框架底层广泛使用。
5. 弱引用强引用
使用ThreadLocal最大一个缺点就是其会发生内存泄漏,那什么原因造成它会发生内存泄漏呢?由于ThreadLocalMap的key是弱引用,而Value是强引用。
查看源码:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
那什么是弱引用?什么是强引用呢?要讨论ThreadLocal内存泄漏问题,先得了解什么是对象的弱引用,什么是对象的强引用?
java中对象有四种引用关系:强引用、弱引用、软引用、虚引用。
强引用:默认的对象都是强引用,如:String s = “tony”; 由于对象被其他对象所调用,GC干不掉。
弱引用:弱引用生命周期很短,不论当前内存是否充足,都只能存活到下一次垃圾收集之前。也就是说GC时就会被干掉。
6.内存泄漏
对象申请后不释放,积累多了就会发生内存泄漏。这种情况实际开发中非常常见,但非常难以监测。我们可以由很多现象的发生推测内存有泄漏的情况。例如:刚开始我们使用一款软件时操作非常流畅,但使用时间长了,软件开始卡顿,重启一下,又飞快如飞。这就是典型的内存泄漏。
ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
如何避免泄漏?
既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。
如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。
7.线程锁和ThreadLocal的区别
线程锁实现了线程的同步,线程排队按顺序执行。线程阻塞,前面没有执行完成,后面就只能等待,前面的执行完成,后面才能进行执行。
概括来说,对于多线程资源共享的问题,线程锁同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。