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;
        }

        @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,这样会很快被垃圾回收器发现,从而回收。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值