• Java Concurrency


    共享数据是多线程应用最常见的问题之一,但有时我们需要为每个线程保存一份独立的变量。Java API 提供了 ThreadLocal 来解决这个问题。

    一个 ThreadLocal 作用的例子:

    import java.util.Date;
    
    public class Main {
        
        public static void main(String[] args) {
            Runnable task = new Runnable() {
                
                private ThreadLocal<Date> dateVar = new ThreadLocal<Date>();
                
                public void run() {
                    dateVar.set(new Date());
                    System.out.printf("%s, GET dateVar: %s
    ", Thread.currentThread().getName(), dateVar.get());
                    try {
                        Thread.sleep(5000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.printf("%s, FINAL dateVar: %s
    ", Thread.currentThread().getName(), dateVar.get());
                }
            };
            
            for (int i = 0; i < 3; i++) {
                String threadName = "Thread" + (i + 1);
                Thread thread = new Thread(task, threadName);
                thread.start();
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
        }
    }

    观察运行结果:

    Thread1, GET dateVar: Fri Oct 14 22:06:33 CST 2016
    Thread2, GET dateVar: Fri Oct 14 22:06:34 CST 2016
    Thread3, GET dateVar: Fri Oct 14 22:06:35 CST 2016
    Thread1, FINAL dateVar: Fri Oct 14 22:06:33 CST 2016
    Thread2, FINAL dateVar: Fri Oct 14 22:06:34 CST 2016
    Thread3, FINAL dateVar: Fri Oct 14 22:06:35 CST 2016

    可以看到每个线程都共用一个 Task 实例,线程之间间隔 1 秒启动。线程执行 run 方法的时候首先将 dateVar 的值设置为系统当前时间并打印 dateVar 值,然后线程会休眠 5 秒,最后再打印 dateVar 的值。注意到 run 方法设置 dateVar 值与最后打印 dateVar 值间隔 5 秒,而下一个线程启动时只间隔 1 秒,在当前线程打印 dateVar 之前,下个线程甚至是下下个线程已经重置 dateVar 的值,但是每个线程最后打印 dateVar 值的时候仍然是显示该线程最初设置的值。可见,每个线程中的 dateVar 并不会被其他线程所干扰。

    再看下没有使用 ThreadLocal 的情况。

    import java.util.Date;
    
    public class Main2 {
        
        public static void main(String[] args) {
            Runnable task = new Runnable() {
                
                private Date dateVar;
                
                public void run() {
                    dateVar = new Date();
                    System.out.printf("%s, GET dateVar: %s
    ", Thread.currentThread().getName(), dateVar);
                    try {
                        Thread.sleep(5000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.printf("%s, FINAL dateVar: %s
    ", Thread.currentThread().getName(), dateVar);
                }
            };
            
            for (int i = 0; i < 3; i++) {
                String threadName = "Thread" + (i + 1);
                Thread thread = new Thread(task, threadName);
                thread.start();
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    观察运行结果:

    Thread1, GET dateVar: Fri Oct 14 22:17:38 CST 2016
    Thread2, GET dateVar: Fri Oct 14 22:17:39 CST 2016
    Thread3, GET dateVar: Fri Oct 14 22:17:40 CST 2016
    Thread1, FINAL dateVar: Fri Oct 14 22:17:40 CST 2016
    Thread2, FINAL dateVar: Fri Oct 14 22:17:40 CST 2016
    Thread3, FINAL dateVar: Fri Oct 14 22:17:40 CST 2016

    可以看到,当直接使用 Date 类型时,线程最终打印 dateVar 的值与最初设置的值不一致。这个是因为所有的线程共享一个 Task 实例,所以 dateVar 是共享数据,在多个线程竞争时,造成数据不一致。

    ThreadLocal 的初始化

    可以通过覆盖 initialValue 方法初始化 ThreadLocal 的值。

    ThreadLocal<Date> dateVar = new ThreadLocal<Date>() {
        @Override
        protected Date initialValue() {
            return new Date();
        }
    };

    InheritableThreadLocal

    如果在线程 A 创建了子线程 B,那么线程 A 和 线程 B 都是各自维护一份 ThreadLocal 值,线程 A 的 ThreadLocal 值不会传递给子线程 B。Java API 提供了 InheritableThreadLocal 类,它是 ThreadLocal 的子类。如果使用 InheritableThreadLocal,那么在线程 A 创建子线程 B,线程 A 和 线程 B 仍然都是各自维护一份 InheritableThreadLocal 值,但是现场 A 的 InheritableThreadLocal 值则会传递给子线程 B。可以重写 childValue 方法修改从父现场继承的 InheritableThreadLocal 值。

    public static ThreadLocal<Date> dateVar = new InheritableThreadLocal<Date>() {
        protected Date initialValue() {
            return new Date();
        };
        
        protected Date childValue(Date parentValue) {
            return new Date(parentValue.getTime() + 1000L);
        };
    };
  • 相关阅读:
    黄聪:Visual Studio快速封装字段方法
    黄聪:在vs2008中设置jquery智能提示
    黄聪:Linq初级班 Linq to DataSet体验(单表、多表联合查询JOIN语法)
    mysql分区
    为什么使用框架
    阅读杂记(RSA,PDO)
    Golang之继承模拟
    php中$_REQUEST一个注意点
    记录mysql性能查询过程
    知识杂记
  • 原文地址:https://www.cnblogs.com/huey/p/5961527.html
Copyright © 2020-2023  润新知