用ThreadLocal为线程生成唯一标识

本文介绍了如何利用ThreadLocal在多线程环境中为每个线程生成唯一的标识。通过示例代码解释了ThreadLocal的工作机制,包括它的初始值设定和线程局部变量的获取。示例展示了如何创建一个线程序号生成工具,以及如何在多线程对象中使用该工具。此外,还提供了一个简单的ThreadLocal实现,以便更好地理解其工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在多线程编程中,有时候需要自动为每个启动的线程生成一个唯一标识,这个时候,通过一个ThreadLocal变量来保存每个线程的标识是最有效、最方便的方式了。

 

下面是JDK帮助文档的说明:

-------------------------------------------------------------

public class ThreadLocal<T> extends Object


 

该类提供了线程局部变量。这些变量不同于它们的普通对应物,因为访问一个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的私有静态字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。 

例如,在下面的类中,私有静态 ThreadLocal 实例(serialNum)为调用该类的静态 SerialNum.get() 方法的每个线程维护了一个“序列号”,该方法将返回当前线程的序列号。(线程的序列号是在第一次调用 SerialNum.get() 时分配的,并在后续调用中不会更改。)

 

每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

 

ThreadLocal() 
          创建一个线程本地变量。

 

方法摘要
 Tget() 
          返回此线程局部变量的当前线程副本中的值。
protected  TinitialValue() 
          返回此线程局部变量的当前线程的初始值。
 voidremove() 
          移除此线程局部变量的值。
 voidset(T value) 
          将此线程局部变量的当前线程副本中的值设置为指定值。

 

其中,还给出了一个很有用的例子片段,是一个线程序号维护工具,很有用,我经过完善后如下:

 

/** 
* 线程序号标识生成工具 

* @author leizhimin 2008-8-21 21:28:54 
*/
 
public class SerialNum { 
    //类级别的线程编号变量,指向下一个线程的序号 
    private static Integer nextNum = 0; 
    //定义一个ThreadLocal变量,存放的是Integer类型的线程序号 
    private static ThreadLocal<Integer> threadNo = new ThreadLocal<Integer>() { 
        //通过匿名内部类的方式定义ThreadLocal的子类,覆盖initialValue()方法 
        public synchronized Integer initialValue() { 
            return nextNum++; 
        } 
    }; 

    /** 
     * 获取线程序号 
     * 
     * @return 线程序号 
     */
 
    public int getNextNum() { 
        return threadNo.get().intValue(); 
    } 
}

 

/** 
* 一个多线程对象,其中有个私有变量SerialNum,用来保存该对象线程的序号 

* @author leizhimin 2008-8-21 21:52:47 
*/
 
class MultiThreadObject extends Thread { 
    //线程序号变量 
    private SerialNum serialNum; 

    public MultiThreadObject(SerialNum serialNum) { 
        //初始化线程序号保存变量 
        this.serialNum = serialNum; 
    } 

    /** 
     * 一个示意性的多线程业务方法 
     */
 
    public void run() { 
        System.out.println("线程" + Thread.currentThread().getName() + "的序号为" + serialNum.getNextNum()); 
    } 
}

 

/** 
* 测试线程序号工具 

* @author leizhimin 2008-8-21 21:50:44 
*/
 
public class TestTreadLocal { 
    public static void main(String[] args) { 
        SerialNum serialNum = new SerialNum(); 
        MultiThreadObject m1 = new MultiThreadObject(serialNum); 
        MultiThreadObject m2 = new MultiThreadObject(serialNum); 
        MultiThreadObject m3 = new MultiThreadObject(serialNum); 
        MultiThreadObject m4 = new MultiThreadObject(serialNum); 

        m1.start(); 
        m2.start(); 
        m3.start(); 
        m4.start(); 

        //下面的test方法是在主线程中,当前线程是 
        //test(); 
    } 

    public static void test(){ 
        SerialNum serialNum = new SerialNum(); 
        System.out.println(serialNum.getNextNum()); 
        SerialNum serialNum2 = new SerialNum(); 
        System.out.println(serialNum2.getNextNum()); 
    } 
}

 

运行结果:

线程Thread-0的序号为0 
线程Thread-1的序号为1 
线程Thread-2的序号为2 
线程Thread-3的序号为3 

Process finished with exit code 0 

 

JDK中这个例子的高明之处在于巧妙使用静态变量,结合ThreadLocal的特性,在构件ThreadLocal的时候,通过覆盖子类的方法来改写序号。从而达到为每个线程生成序号的目的。

 

 

ThreadLocal理解

ThreadLocal是线程的局部变量,常用来为每个线程提供独立的变量副本(可理解拷贝),没个线程可以随意改变其副本,而不会影响原版。

ThreadLocal是Java在通过语言的扩展而来的,并非从语法级(原生)支持。这是导致ThreadLocal概念不好理解的主要原因。有两种途径可以帮助理解,一是查看ThreadLocal的源代码(JAVA已经开源了),其次是通过一个简单的ThreadLocal实现来看其原理。源码都可以看,但比较复杂。还是看个简单ThreadLocal实现吧:

 

import java.util.Map; 
import java.util.Collection; 
import java.util.Collections; 
import java.util.HashMap; 

/** 
* 一个示意性的ThreadLocal实现,与JDK中ThreadLocal的API对等 

* @author leizhimin 2008-8-21 22:45:13 
*/
 
public class SimpleThreadLocal { 
    //一个线程Map,用来存放线程和其对应的变量副本 
    private Map threadMap = Collections.synchronizedMap(new HashMap()); 

    public void set(Object object) { 
        threadMap.put(Thread.currentThread(), object); 
    } 

    public Object get() { 
        Thread currentThread = Thread.currentThread(); 
        Object obj = threadMap.get(currentThread); 
        if (obj == null && !threadMap.containsKey(currentThread)) { 
            obj = initialValue(); 
            threadMap.put(currentThread, obj); 
        } 
        return obj; 
    } 

    public void remove() { 
        threadMap.remove(Thread.currentThread()); 
    } 

    protected Object initialValue() { 
        return null
    } 
}

 

上面这个类可以替代JDK中ThreadLocal来实现同样的功能。我已经试过了,只需要修改SerialNum的实现为:

 

/** 
* 线程序号标识生成工具 

* @author leizhimin 2008-8-21 21:28:54 
*/
 
public class SerialNum { 
    //类级别的线程编号变量,指向下一个线程的序号 
    private static Integer nextNum = 0; 
    //定义一个ThreadLocal变量,存放的是Integer类型的线程序号 
//    private static ThreadLocal<Integer> threadNo = new ThreadLocal<Integer>() { 
    private static SimpleThreadLocal threadNo = new SimpleThreadLocal() { 
        //通过匿名内部类的方式定义ThreadLocal的子类,覆盖initialValue()方法 
        public synchronized Integer initialValue() { 
            return nextNum++; 
        } 
    }; 

    /** 
     * 获取线程序号 
     * 
     * @return 线程序号 
     */
 
    public int getNextNum() { 
        return (Integer)threadNo.get(); 
    } 
}

 

线程Thread-0的序号为0 
线程Thread-1的序号为1 
线程Thread-2的序号为2 
线程Thread-3的序号为3 

Process finished with exit code 0 

 

可以看出,JDK中TreadLocal实现只是考虑更周密一些罢了,思想是一致的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值