重生之学java的第十二天

一、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();
    }
}

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值