ThreadLocal原理与使用

一. 是什么?

一般而言,全局变量都是由线程共享的。引入ThreadLocal,可以给多个线程提供同一个变量的多个拷贝(副本)。

二. 为什么要用?

突出该技术概念有什么优点是值得投入使用的

  1. 简单
    不需要考虑多线程容易引起的数据一致性问题。
    降低了编程复杂度,能够直接以单线程思维编程。

  2. 性能好
    更少的同步等待,更少占用cpu时间片

  3. 安全
    具有线程安全特性

三. 用在什么场景?

在进程中,默认变量都是由各个线程共享的。当多个线程对共享变量进行写时,可能会导致读写共享变量不一致的问题。

  1. 资源持有
    当多个线程需要对同一个变量拥有独立的拷贝时,避免线程间对共享变量读写影响。

  2. 线程一致性
    协助需要保持资源一致性的操作,如数据库事务变更数据后读取,降低编程复杂度。

  3. 线程安全
    在进行多线程改造时,容易从单线程的实现角度转换为多线程实现,降低编程复杂度。

  4. 并发计算
    多个线程分发计算时,方便每个线程累计一部分的计算结果值

四. 基本api的使用

在java中,内部原理使用是通过具有线程安全的Hashtable来实现ThreadLocal

  1. ThreadLocal<T>
    一般搭配static一起使用,在全局中声明一个线程多副本存储的变量
  2. initialValue
    实例化ThreadLocal时,用于重写的方法。当get方法为null时,返回默认值。
  3. get/set
    获取/设置当前线程中对应的拷贝副本
  4. remove
    移除当前线程中对应的拷贝副本
简单练习
/**
 * @Title ThreadLocal概念学习
 * @Description 简要api学习
 * @Author Heavent
 * @Date 2020-02-02
 * @Version 1.0.0
 **/
public class ThreadLocalDemo {

    protected static ThreadLocal<String> threadId = new ThreadLocal(){

        // 重写该方法,允许ThreadLocal实例通过get获取值时,返回一个默认值
        protected String initialValue() {
            return "initial: thread->" + Thread.currentThread().getId();
        }

    };

    public static void main(String[] args) {

        System.out.println("============== main thread =================");
        // 设置当前线程中对应threadId的拷贝副本
        threadId.set("main-thread->" + Thread.currentThread().getId());
        // 设置当前线程中对应threadId的拷贝副本
        System.out.println(threadId.get());

        new Thread(() -> {
            System.out.println("------------------ 1 thread ------------------");
            threadId.set("rename-1 thread->" + Thread.currentThread().getId());
            threadId.remove();
            System.out.println("获取默认的initialValue定义的值:" + threadId.get());
        }).start();

        new Thread(() -> {
            System.out.println("------------------ 2 thread ------------------");
            System.out.println("获取默认的initialValue定义的值:" + threadId.get());
        }).start();

    }

}

五. 分布式计算简单案例模型

对于远程分布式汇总计算,可用ThreadLocal减少synchronized频次的使用,实现最终数据一致性

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

/**
 * @Title 分布式计算案例
 * @Description 通过10个线程计算100次后,将各线程的计算结果汇总
 * @Author Heavent
 * @Date 2020-02-02
 * @Version 1.0.0
 **/
public class DistributedComputingDemo {

    /**
     * 由于ThreadLocal没有提供遍历 多线程存储account变量副本 的api
     * 声明map来引用每个线程中account变量副本的情况
     * 需要主要的是要自定义对象引用变量,不能直接使用Double/Integer/Float
     */
    static Map<Thread, RefVal<Double>> allThreadMap = new HashMap<>();

    static ThreadLocal<RefVal<Double>> account = new ThreadLocal(){
        @Override
        protected RefVal<Double> initialValue() {
            RefVal<Double> defVal = new RefVal<>();
            defVal.setVal(0d);
            /**
             * 此处线程不安全,需要增加同步操作
             * 此处同步属于低频次同步,多少线程加入,则有多少次同步
             * 当使用ThreadLocal并不一定完全避免同步,但要尽量将同步减少到最低
             */
            synchronized (DistributedComputingDemo.class) {
                allThreadMap.put(Thread.currentThread(), defVal);
            }
            return defVal;
        }
    };

    /**
     * 累加运算 10次
     */
    public void  accumulation() throws InterruptedException {
        for (int i=0; i<10; i++) {
            Thread.sleep(50);
            // 模拟远程获取的值为1,便于汇总验证
            double res = 1;
            RefVal<Double> refVal = account.get();
            refVal.setVal(refVal.getVal() + res);
        }
    }

    /**
     * 获取最终的累加值
     */
    public Double getAccountRes(){
        // 使用map筛选出所有的value,再通过reduce进行累加
        return allThreadMap.entrySet().stream().map(entry -> entry.getValue().getVal())
                                      .reduce((x, y) -> x + y).get();
    }

    public static void main(String[] args) {

        try {

            // 线程数
            int threadCore = 10;

            /**
             *  CountDownLatch用于协调多个线程之间的通信
             *  允许一个线程暂时挂起,等待其它线程执行完毕后,再继续执行
             */
            CountDownLatch downLatch = new CountDownLatch(threadCore);

            DistributedComputingDemo demo = new DistributedComputingDemo();

            // 开启10个线程累加计算, 每个线程将累计10次, 每次累计加1,汇总值应为100
            for (int i=0; i<threadCore; i++) {
                new Thread(() -> {
                    try {
                        demo.accumulation();
                        System.out.printf("thread-%s was run...\r\n" , Thread.currentThread().getId());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // 减少一次线程计数
                        downLatch.countDown();
                    }
                }).start();
            }

            // 多线程完成后继续执行main线程
            downLatch.await();
            System.out.println("=================  获取多线程计算后的汇总值  =================");
            System.out.println(demo.getAccountRes());

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

/**
 * 引用每一个线程的副本对象,
 * 因此需要声明一个引用对象, 便于全局获取
 * @param <T>
 */
class RefVal<T> {

    T val;

    public T getVal() {
        return val;
    }

    public void setVal(T val) {
        this.val = val;
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值