ThreadLock

时间: 2023-07-09 admin 互联网

ThreadLock

ThreadLock

除了控制资源的访问,我们还可以增加资源来保证所有对象的线程安全。比如,让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;}@Overridepublic 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;}@Overridepublic 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);elsecreateMap(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,这样会很快被垃圾回收器发现,从而回收。