除了控制资源的访问,我们还可以增加资源来保证所有对象的线程安全。比如,让100个人填写个人信息表,如果只有一只比,那么大家就得挨个填写,对于管理人员来说必须保证大家不会去哄抢这仅有的一支笔,否则,谁也填不完。从另外一个角度出发,我们干脆就准备100支笔,人手一只,那么所有人都可以各自为营,很快就能填完表格。如果锁是第一种思路,那么ThreadLock就是使用第二种思路了。
一,简单使用
从ThreadLock的名字上来看,这是一个线程的局部变量。也就是说,只有当前线程可以访问。既然只有当前线程可以访问,那当然是线程安全的。
package com.example.thread;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by mazhenhua on 2017/3/15.
*/
public class ThreadLockTest {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
Date f = sdf.parse("2017-01-15 15:22:" + i/60);
System.out.println(i + ":" + f);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i ++) {
es.execute(new ParseDate(i));
}
}
}
执行上面的代码会得到一个异常:
这是由于SimpleDateFormat 类不是线程安全所导致的,如果出现这样的问题,解决思路肯定是对SimpleDateFormat 进行加锁,一次只有一个线程访问,那么这次我们换个思路来做我们使用ThreadLock
package com.example.thread;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by mazhenhua on 2017/3/15.
*/
public class ThreadLockTest {
//private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
static ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<SimpleDateFormat>();
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
if (t1.get() == null) { // 当前线程没有SimpleDateFormat对象,则创建一个
t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
Date f = t1.get().parse("2017-01-15 15:22:" + i/60);
System.out.println(i + ":" + f);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i ++) {
es.execute(new ParseDate(i));
}
}
}
如果当前线程不持有SimpleDateFormat对象,则创建一个,有就拿来用。
从上面代码中可以看到,为每一个线程分配一个对象来工作,并不是由ThreadLock来完成的,而是需要在应用层面保证的,ThreadLock只是起到了一个容器的作用。
二,实现原理
我们需要关注的是ThreadLock的set()和get()方法,先来看set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
在set时,首先获得当前线程对象,然后通过getMap()拿到线程的,ThreadLockMap,并将值设入ThreadLocalMap 中,而ThreadLocalMap 可以理解为一个Map(虽然不是,但是可以简单的理解成HashMap),但他是定义在Thread内部成员
ThreadLocal.ThreadLocalMap threadLocals = null;
而设置到ThreadLocal中的数据,也正是写入threadLocals 这个Map,其中key为ThreadLocalMap 当前对象,value就是我们需要的值。而threadLocals 本身就保存了当前自己所在线程的所有‘局部变量’,也就是一个ThreadLock变量的集合。
在进行get操作时,自然就是将这个Map中的数据拿出来
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
在了解了ThreadLock后,就会引出一个问题,就是这些变量都维护在Thread内部的ThreadLocalMap 中,这也意味着线程不退出,对象引用将一直存在。
在线程退出的时候,Thread类会做一些清理工作,例如:
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
如果我们用的是线程池,那就是说,当前线程未必会退出。如果这样,将一些大的对象设置进去,用了几次不用了,那么就会造成系统内存泄露。
如果你希望及时回收对象,最好使用,ThreadLock.remove()方法,将这个变量移除。或者像释放普通变量一样,ThreadLock = null,这样会很快被垃圾回收器发现,从而回收。