一、Threadlocal对象的使用
膜拜大佬!
说到Threadlocal,先引入问题:
什么是SpringMVC的线程安全问题?
在默认配置下,SpringMVC是单例多线程,意思就是controller、service、dao层同一个类的对象只有一个。Java虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
局部变量的固有属性之一就是封闭在执行线程中。他们位于执行线程的栈中,其他的线程无法访问这个栈。所以说,任何无状态单例都是线程安全的。Spring的根本就是通过大量这种单例构建起系统,以事务脚本的方式提供服务。
返回正题
Threadlocal又是什么呢?
来自百科的回答:
ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
Threadlocal原理:
用到Thread对象中的一个ThreadLocalMap类型的变量threadLocals, 负责存储当前线程的关于Connection的对象, 变量为Key, 以新建的对象为Value; 这样的话, 线程第一次读取的时候如果不存在就会调用ThreadLocal的initialValue方法创建一个Connection对象并且返回。
产生问题:“由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不仅严重影响程序执行效率,还可能导致服务器压力巨大。”
解决方案:ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大!
常用方法 get,set,remove 简析
get
get( )
此方法是ThreadLocal最重要的方法之一,该方法返回此线程局部变量的当前线程副本中的值。 大概可分为以下几步:
(1) 先获取当前线程,然后再从线程中得到ThreadLocalMap。
(2) 然后使用ThreadLocal对象的threadLocalHashCode进行散列计算,得到一个数组的index
(3) 从Table数组中得到Entry,再对比Entry的key是不是和当前的ThreadLocal相等,如果相等就返回此Entry的value
(4) 如果上一步中得到的Entry与当前ThreadLocal不相等,则会在方法getEntryAfterMiss中进行遍历Entry数组table中的每一个元素,如果找不到就返回null。而且在遍历的过程中会顺便清理一下废弃的Entry。
set
set(T value)
此方法将此线程局部变量的当前线程副本中的值设置为指定值。
set线程本地变量步骤如下:
(1) 首先依然是获取此线程的ThreadLocalMap
(2) Map不为null时往map中插入数据,否侧创建map并插入数据
(3) 具体的set方法依然是先遍历Entry数组中所有的的Entry,然后依次对比每个Entry的key是否等于当前ThreadLocal,如果相等则直接替换现有Entry的value。如果Entry的Key为null,则立马清理废弃的Entry,并用新的Entry来替换此卡槽。
(4) 如果遍历完都没有return,则在在table中相应卡槽下新建Entry对象
remove
remove()
则相对简单,直接遍历ThreadLocalMap中Entry数组table,找到对应的Entry,将Entry的key置为null,然后再清理相应的Entry。
举个例子:
得到结果:
二、使用ReentrantLock测试线程的安全
创建Ticket
public class Ticket implements Runnable{
private int alltickets = 60;
private boolean flag = true;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(alltickets>0) {
if (this.flag == true) {
try {
this.lock.lock();
Thread.sleep(300);
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
this.lock.unlock();
}
} else {
break;
}
}
}
public void buy() throws InterruptedException {
if(this.alltickets<=0)
{
System.out.println("没票可买了"+Thread.currentThread().getName());
this.flag = false;
return;
}
else
{
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"买了第"+this.alltickets--+"张票 ");
}
}
}
创建TestThread
public class TestThread {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Ticket t = new Ticket();
new Thread(t, "小周").start();
new Thread(t, "小泽").start();
new Thread(t, "小君").start();
}
}
运行结果: