zoukankan      html  css  js  c++  java
  • ThreadLocal的基本用法

    1.基本用法

    Java ThreadLocal类允许您创建只能由同一线程读写的变量。因此,即使两个线程正在执行相同的代码,并且代码引用了相同的ThreadLocal变量,这两个线程也不能看到彼此的ThreadLocal变量。因此,Java ThreadLocal类提供了一种使代码线程安全的简单方法。

    //创建
    private ThreadLocal threadLocal = new ThreadLocal();
    //一旦创建了ThreadLocal,就可以使用它的set()方法设置要存储在其中的值。
    threadLocal.set("A thread local value");
    //获取值
    String threadLocalValue = (String) threadLocal.get();
    //移除一个值
    threadLocal.remove();
    

    2. 普通ThreadLocal

    //您可以创建具有泛型类型的ThreadLocal。使用泛型类型时,只能将泛型类型的对象设置为ThreadLocal的值。
    //此外,您不必对get()返回的值进行类型转换。
    private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();
    //现在只能在ThreadLocal实例中存储字符串。另外,您不需要对从ThreadLocal获得的值进行类型转换
    myThreadLocal.set("Hello ThreadLocal");
    String threadLocalValue = myThreadLocal.get();
    

    3.初始化ThreadLocal的值

    可以为Java ThreadLocal设置一个初始值,该值将在第一次调用get()时使用.
    有两种方式指定ThreadLocal初始值:

    3.1创建一个ThreadLocal子类,该子类覆盖initialValue()方法:

    //为Java ThreadLocal变量指定初始值的第一种方法是创建ThreadLocal的子类,该子类覆盖了它的initialValue()方法。
    //创建ThreadLocal子类的最简单方法是直接在创建ThreadLocal变量的地方创建一个匿名子类。下面是建匿名子类的示例,
    //该子类覆盖了initialValue()方法
    private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
        @Override protected String initialValue() {
            return String.valueOf(System.currentTimeMillis());
        }
    };
    //注意,不同的线程仍然会看到不同的初始值。每个线程将创建自己的初始值。
    //只有当从initialValue()方法返回完全相同的对象时,所有线程才会看到相同的对象。
    //但是,首先使用ThreadLocal的全部意义在于避免不同的线程看到相同的实例
    
    

    3.2创建具有Supplier接口实现的ThreadLocal。

    //为Java ThreadLocal变量指定初始值的第二种方法是使用其内部的静态工厂方法(Supplier),将Supplier接口实现作为参数传递。
    //这个Supplier实现为ThreadLocal提供初始值。
    //下面是一个使用其withInitial()静态工厂方法创建ThreadLocal的示例,该方法传递一个简单的供应商实现作为参数
    ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {
        @Override
        public String get() {
            return String.valueOf(System.currentTimeMillis());
        }
    });
    //Java8 lambda表达式的写法
    ThreadLocal threadLocal = ThreadLocal.withInitial(
            () -> { return String.valueOf(System.currentTimeMillis()); } );
    //还可以更短
    ThreadLocal threadLocal3 = ThreadLocal.withInitial(
            () -> String.valueOf(System.currentTimeMillis()) );
    

    4.延迟设置ThreadLocal的值

    //在某些情况下,您不能使用设置初始值的标准方法。例如,您可能需要一些在创建ThreadLocal变量时不可用的配置信息。
    //在这种情况下,可以延迟设置初始值。
    //下面是如何在Java ThreadLocal上惰性地设置初始值的示例
    public class MyDateFormatter {
    
        private ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<>();
    
        public String format(Date date) {
            SimpleDateFormat simpleDateFormat = getThreadLocalSimpleDateFormat();
            return simpleDateFormat.format(date);
        }
        
        private SimpleDateFormat getThreadLocalSimpleDateFormat() {
            SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get();
            if(simpleDateFormat == null) {
                simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                simpleDateFormatThreadLocal.set(simpleDateFormat);
            }
            return simpleDateFormat;
        }
    }
    //注意format()方法是如何调用getThreadLocalSimpleDateFormat()方法来获取Java SimpleDatFormat实例的。
    //如果在ThreadLocal中没有设置SimpleDateFormat实例,那么就会在ThreadLocal变量中创建并设置一个新的SimpleDateFormat。
    //一旦线程在ThreadLocal变量中设置了自己的SimpleDateFormat,就会对该线程使用相同的SimpleDateFormat对象继续前进。
    //但只是为了那条线。每个线程创建自己的SimpleDateFormat实例,因为它们不能看到在ThreadLocal变量上设置的其他实例。
    
    //SimpleDateFormat类不是线程安全的,因此多个线程不能同时使用它。为了解决这个问题,
    //上面的MyDateFormatter类为每个线程创建一个SimpleDateFormat,
    //因此调用format()方法的每个线程将使用自己的SimpleDateFormat实例。
    

    5.多线程情况下使用 ThreadLocal

    如果您计划从传递给Java线程池或Java ExecutorService的任务内部使用Java ThreadLocal,请记住,您不能保证哪个线程将执行您的任务。
    但是,如果您所需要的只是确保每个线程使用某个对象的自己的实例,那么这不是问题。然后,您可以将Java ThreadLocal与线程池或ExecutorService一起使用。

    5.1 完整的ThreadLocal 示例

    public class ThreadLocalExample {
    
        public static void main(String[] args) {
            MyRunnable sharedRunnableInstance = new MyRunnable();
    
            Thread thread1 = new Thread(sharedRunnableInstance);
            Thread thread2 = new Thread(sharedRunnableInstance);
    
            thread1.start();
            thread2.start();
    
            thread1.join(); //wait for thread 1 to terminate
            thread2.join(); //wait for thread 2 to terminate
        }
    
    }
    //
    public class MyRunnable implements Runnable {
    
        private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
    
        @Override
        public void run() {
            threadLocal.set( (int) (Math.random() * 100D) );
    
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
    
            System.out.println(threadLocal.get());
        }
    }
    
    //这个例子创建了一个MyRunnable实例,
    //它被传递给两个不同的线程。两个线程都执行run()方法,因此在ThreadLocal实例上设置不同的值。
    //如果对set()调用的访问是同步的,并且它不是ThreadLocal对象,那么第二个线程就会覆盖第一个线程设置的值。
    //但是,因为它是ThreadLocal对象,所以两个线程不能看到彼此的值。因此,它们设置和获取不同的值。
    
    

    6.可继承(Inheritable) ThreadLocal

    InheritableThreadLocal类是ThreadLocal的子类。与每个线程在ThreadLocal中都有自己的值不同,
    InheritableThreadLocal将对值的访问权授予一个线程和该线程创建的所有子线程。下面是一个完整的Java InheritableThreadLocal示例:

    public class InheritableThreadLocalBasicExample {
    
        public static void main(String[] args) {
    
            ThreadLocal<String> threadLocal = new ThreadLocal<>();
            InheritableThreadLocal<String> inheritableThreadLocal =
                    new InheritableThreadLocal<>();
    
            Thread thread1 = new Thread(() -> {
                System.out.println("===== Thread 1 =====");
                threadLocal.set("Thread 1 - ThreadLocal");
                inheritableThreadLocal.set("Thread 1 - InheritableThreadLocal");
    
                System.out.println(threadLocal.get());
                System.out.println(inheritableThreadLocal.get());
    
                Thread childThread = new Thread( () -> {
                    System.out.println("===== ChildThread =====");
                    System.out.println(threadLocal.get());
                    System.out.println(inheritableThreadLocal.get());
                });
                childThread.start();
            });
    
            thread1.start();
    
            Thread thread2 = new Thread(() -> {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("===== Thread2 =====");
                System.out.println(threadLocal.get());
                System.out.println(inheritableThreadLocal.get());
            });
            thread2.start();
        }
    }
    
    
    //这个例子创建了一个普通的Java ThreadLocal和一个Java InheritableThreadLocal。
    //然后,示例创建一个线程,该线程设置ThreadLocal和InheritableThreadLocal的值——然后创建一个子线程,
    //该子线程访问ThreadLocal和InheritableThreadLocal的值。只有InheritableThreadLocal的值对子线程是可见的。
    //最后,示例创建了第三个线程,该线程也尝试访问ThreadLocal和InheritableThreadLocal——但是它看不到第一个线程存储的任何值
    //输出值:
    ===== Thread 1 =====
    Thread 1 - ThreadLocal
    Thread 1 - InheritableThreadLocal
    ===== ChildThread =====
    null
    Thread 1 - InheritableThreadLocal
    ===== Thread2 =====
    null
    null
    
  • 相关阅读:
    winform combobox SelectedText值为空
    【转】C#、面向对象、设计模式学习
    oracle查询分区表中的数据
    ORA-14402:更新分区关键字列将导致分区更改
    winform列标题高度无法改变
    【转】c#中@的3种作用
    winform 弹出的form显示在最前端
    winform datagridview数据显示不全
    【转】如何把CD上的音乐拷贝到电脑上
    开关WIFI脚本
  • 原文地址:https://www.cnblogs.com/paidaxing7090/p/14055199.html
Copyright © 2011-2022 走看看