多线程基础总结四--ThreadLocal
2010-01-22 00:00:00 来源:WEB开发网这个例子中ThreadLocalSample继承自Thread持有OperationSample三个版本中的一个引用,并且在线程运行时执行printAndIncrementNum()方法。
首先看版本1:OperationSample有个共享变量num,printAndIncrementNum()方法没有同步保护,方法就是循环给 num赋新值并打印改变值的线程名。因为没有任何的同步保护,所以原本打算每个线程打印出的值是相邻递加10的结果变成了不确定的递加。有可能线程1的循环第一次打印0,第二次就打印50。这时候我们使用被注释的方法声明,结果就是预想的同一个线程的两次结果是相邻的递加,因为同一时刻只有一个线程获得 OperationSample实例的隐式锁完成循环释放锁。
再看版本2:假设我们有个递增10的简单计数器,但是是对每个线程的计数。也就是说我们有一个Integer计数器负责每个线程的计数。虽然它是有状态的,会变的,但是因为每个线程之间不需要共享变化,所以可以用ThreadLocal管理这个Integer。在这里看到我们的ThreadLocal变量的initialValue()方法被覆写了,这个方法的作用就是当调用ThreadLocal的get()获取线程绑定的副本时如果还没绑定则调用这个方法在Map中添加当前线程的绑定映射。这里我们返回0,表示每个线程的初始副本在ThreadLocal的Map的纪录都是0。再看 printAndIncrementNum()方法,没有任何的同步保护,所以多个线程可以同时进入。但是,每个线程通过threadArg.get() 拿到的仅仅是自己的Integer副本,threadArg.set(num + 10)的也是自己的副本值。所以结果就是虽然线程的两次循环打印有快有慢,但是每个线程的两次结果都是0和10。
最后是版本3:和版本2的不同在于新加了一个uniqueId的变量。这个变量是java.util.concurrent.atomic包下的原子变量类。这是基于硬件支持的CAS(比较交换)原语的实现,所以保证了++,--,+=,-=等操作的原子性。所以在ThreadLocal变量的 initialValue()方法中使用uniqueId.getAndIncrement()将为每个线程初始化唯一不会重复的递加1的Integer 副本值。而结果就会变成5个线程的首次打印是0~4的5个数字,第二次每个线程的打印是线程对应的首次数字加10的值。
对于ThreadLocal的使用,Spring的源码中有大量的应用,主要是要支持Singleton的实例管理,那么自身的一些Singleton的实现内非线程安全的变量,属性要用ThreadLocal隔离共享。同时我们在使用Spring的IOC时也要注意有可能多线程调用的注册到IOC容器的 Singleton型实例是否真的线程安全。另外java.util.concurrent.atomic内的原子变量类简单的提了一下,再看看怎么能瞎编出东西来吧。
更多精彩
赞助商链接