深入学习ThreadLocal

今天深入学习了一下ThreadLocal,这里做一个记录。

目录:
一。ThreadLocal简介
二。ThreadLocal的简单使用
三。ThreadLocal的实现原理
3.1ThreadLocal的set方法
3.2ThreadLocal的get方法
3.3 ThreadLocal的setInitialValue()方法
3.4ThreadLocal的remove()方法
四。ThreadLocal的实际应用
五。ThreadLocal不支持继承性
六。InheritableThreadLocal类
七。ThreadLocal和同步方法解决线程安全问题的区别

一。ThreadLocal简介

 我们都知道,多线程访问共享变量的时候容易发生线程不安全的情况,为了解决线程不安全的问题,我们可以通过同步机制避免非线程安全。还有一种方式是使用ThreadLocal,也就是可以在不同的线程中维护一份共享变量的副本。这样可以在某些场景下使用可以避免非线程安全。
 ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建了一个ThreadLocal变量,那么访问这个变量的每一个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。

二。ThreadLocal的简单使用

public class ThreadLocalDemo {

    private static String currentStr = null;

    private static void init(){
        currentStr = "init";
    }

    public static void main(String[] args) {
        System.out.println("main start");
        init();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1.start");
                System.out.println("thread1-1:"+currentStr);
                currentStr = "thread1-temp-end";
                System.out.println("thread1-2::"+currentStr);
                System.out.println("thread1.end");
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread2.start");
                System.out.println("thread2-1:"+currentStr);
                currentStr = "thread2-temp-end";
                System.out.println("thread2-2:"+currentStr);
                System.out.println("thread2.end");
            }
        });

        thread1.start();
        thread2.start();

        System.out.println("main start");
    }

}

代码运行结果如下:

main start
main start
thread1.start
thread1-1:init
thread2.start
thread2-1:init
thread2-2:thread2-temp-end
thread1-2::thread2-temp-end
thread1.end
thread2.end

由上看到

thread2-2:thread2-temp-end
thread1-2::thread2-temp-end
这两行就可以看出这是一个典型的存在线程安全问题的代码,两个线程操作了同一个变量,有可能会得到与臆想不一致的情况。

下面给出一个相同场景下使用了ThreadLocal的例子:

public class ThreadLocalDemo2 {

    private static ThreadLocal<String> currentStr = new ThreadLocal<>();

    public static void main(String[] args) {
        System.out.println("main start");

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1.start");
                System.out.println("thread1:currentStr1-1:"+currentStr.get());
                currentStr.set("thread1-temp-end");
                System.out.println("thread1:currentStr1-2:"+currentStr.get());
                System.out.println("thread1.end");
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread2.start");
                System.out.println("thread2:currentStr2-1:"+currentStr.get());
                currentStr.set("thread2-temp-end");
                System.out.println("thread2:currentStr2-2:"+currentStr.get());
                System.out.println("thread2.end");
            }
        });

        thread1.start();
        thread2.start();

        System.out.println("main start");
    }

}

他的运行结果如下:

main start
thread1.start
thread1:currentStr1-1:null
main start
thread2.start
thread2:currentStr2-1:null
thread2:currentStr2-2:thread2-temp-end
thread2.end
thread1:currentStr1-2:thread1-temp-end
thread1.end

我们可以看到两个线程虽然共享了同一个currentStr对象,但是在自己线程中对currentStr变量的修改不会对另一个线程的currentStr对象可见,显然这样就避免了多线程共享数据线程不安全的问题。同时也能看出其实两个线程操作的是自己线程对currentStr变量的一个副本,所以不会影响别的线程的这个currentStr变量。

三。ThreadLocal的实现原理

首先我们来看一下Thread 类,会发现Thread类中有两个变量:

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

由上可以看到在Thread中维护了两个 ThreadLocal.ThreadLocalMap类型的变量 threadLocals 和 inheritableThreadLocals
那我们再来看一下,ThreadLocal类中主要有这几个方法:

public void set(T value);
public T get();
private T setInitialValue();
public void remove()

这几个方法主要定义了ThreadLocal设置,获取,设置初始值,和清除的方法。
然后还可以看到ThreadLocal类中有一个静态内部类:ThreadLocalMap
ThreadLocalMap内部其实就维护了一个Entry实体,大概跟HashMap差不多。
对于ThreadLocal有一个理解上的误区就是我们会认为每个线程的变量副本存放到ThreadLocal里面,这么理解是不对的,应该是每个线程的副本存放到了ThreadLocal.ThreadLocalMap类型的一个容器里面,这个容器跟Map类似其实是一个Entry。下面我们来看一下ThreadLocalMap的源码:

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }

这里我省去了很多内容,由上可以ThreadLocalMap是ThreadLocal的一个静态内部类,他的构成主要由Entry来保存数据,而且还是继承的WeakReference(弱引用),这里会给后面一个ThreadLocal的变量容易引起内存泄漏的问题买了一个伏笔,这里暂且不说。
好了,现在我们知道了ThreadLocal修饰的变量其实是存放在一个ThreadLocal.ThreadLocalMap类型的一个Entry实体里面的。那么解析来我们再来看一下ThreadLocal中的

public void set(T value);
public T get();
private T setInitialValue();
public void remove()

的这几个方法。

3.1ThreadLocal的set方法:

public void set(T value) {
		// 1.获取当前线程
        Thread t = Thread.currentThread();
        // 2.以当前线程 为key去查找对应的线程变量,找到对应的value
        ThreadLocalMap map = getMap(t);
        // 3.如果map不为空,则直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量的值
        if (map != null)
            map.set(this, value);
        else
        // 4.如果map为空,说明是首次添加,需要先创建出对应的map,并且key为当前定义的ThreadLocal变量的引用,value作为添加的本地变量的值
            createMap(t, value);
    }

getMap和createMap方法如下:

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

3.2ThreadLocal的get方法:

public T get() {
		// 1.获取当前线程
        Thread t = Thread.currentThread();
        // 2.根据当前线程或者threadLocals变量
        ThreadLocalMap map = getMap(t);
        // 3.如果获取到的threadLocals变量不为空,就可以直接拿到本地变量的值
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 4.如果运行到这里,说明没有获取到threadLocals变量,则调用初始化当前线程threadLocals变量的方法
        return setInitialValue();
    }
private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
protected T initialValue() {
        return null;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

接上面,由上可以看到初始化threalLocals变量时会初始化一个null,这就是为什么上面第二块代码中第一次调用get方法是会得到一个null的原因。

3.3 ThreadLocal的setInitialValue()方法
继续粘一下setInitialValue()方法

private T setInitialValue() {
		// 1.获取一个初始化的value,这个初始化的value为null
        T value = initialValue();
        // 2.获取当前线程
        Thread t = Thread.currentThread();
        // 根据当前线程获取threalLocals变量
        ThreadLocalMap map = getMap(t);
        // 如果能获取到threadLocals变量,则直接设置该值给这个threadLocal变量
        if (map != null)
            map.set(this, value);
        // 如果没有获取到threadLocals变量,则初始化一个map,并把该值设置进去
        else
            createMap(t, value);
        return value;
    }
protected T initialValue() {
        return null;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

3.4ThreadLocal的remove()方法

public void remove() {
		// 1.获取当前线程绑定的threadLocals变量
         ThreadLocalMap m = getMap(Thread.currentThread());
         // 2.如果获取到的map不为空,则移除当前线程中指定的ThreadLocal实例的本地变量
         if (m != null)
             m.remove(this);
     }

这里说一下,我们已经知道了,ThreadLocal修饰的变量每个线程都会复制一个副本存放到自己线程的Entry中,那么ThreadLocal的变量的生命周期和该线程一样,所以ThreadLocal的变量会一直存在,除非线程消亡了,ThreadLocal修饰的变量才会被回收,那么就容易会造成内存泄漏,所以在线程中对于ThreadLocal的变量,如果用完了就最好手动remove()一下。

四。ThreadLocal的实际应用

public class ThreadLocal5 {

    static ThreadLocal<Map<String, String>> localMap = new ThreadLocal<Map<String, String>>(){
        @Override
        protected Map<String, String> initialValue() {
            Map<String, String> map = new HashMap<>(2);
            map.put("startTime", "2020-01-01");
            map.put("endTime", "2021-12-01");
            return map;
        }
    };



    static void print(Map<String, String> map){
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey()+":"+entry.getValue());
        }
    }

    public static void main(String[] args) {
        System.out.println("main-start");

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1-start...");
                System.out.println("thread1-start-第一次输出完了...");
                Map<String, String> map = localMap.get();
                map.put("date", "2021-02-01");
                map.put("startTime", "2020-07-01");
                localMap.set(map);
                print(localMap.get());
                System.out.println("thread1-end...");
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread2-start...");
                System.out.println("thread2-start-第一次输出完了...");
                Map<String, String> map = localMap.get();
                map.put("startTime", "2020-08-01");
                map.put("seq", "12");
                localMap.set(map);
                print(localMap.get());
                System.out.println("thread2-end...");
            }
        });

        thread1.start();
        thread2.start();

        System.out.println("main-end");
    }
}

这里是我写的一个ThreadLocal的map,并且初始化了其中的值,每个变量都会共享localMap变量。

/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/bin/java -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:60397,suspend=y,server=n -javaagent:/Users/jiangtao/Library/Caches/JetBrains/IntelliJIdea2020.1/captureAgent/debugger-agent.jar -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/tools.jar:/Users/jiangtao/IdeaProjects/demo/threaddemo/target/classes:/Users/jiangtao/java/tools/maven_repository/org/springframework/boot/spring-boot-starter-jdbc/2.4.5/spring-boot-starter-jdbc-2.4.5.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/boot/spring-boot-starter/2.4.5/spring-boot-starter-2.4.5.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/boot/spring-boot/2.4.5/spring-boot-2.4.5.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/boot/spring-boot-autoconfigure/2.4.5/spring-boot-autoconfigure-2.4.5.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/boot/spring-boot-starter-logging/2.4.5/spring-boot-starter-logging-2.4.5.jar:/Users/jiangtao/java/tools/maven_repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/jiangtao/java/tools/maven_repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/jiangtao/java/tools/maven_repository/org/apache/logging/log4j/log4j-to-slf4j/2.13.3/log4j-to-slf4j-2.13.3.jar:/Users/jiangtao/java/tools/maven_repository/org/apache/logging/log4j/log4j-api/2.13.3/log4j-api-2.13.3.jar:/Users/jiangtao/java/tools/maven_repository/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar:/Users/jiangtao/java/tools/maven_repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/jiangtao/java/tools/maven_repository/org/yaml/snakeyaml/1.27/snakeyaml-1.27.jar:/Users/jiangtao/java/tools/maven_repository/com/zaxxer/HikariCP/3.4.5/HikariCP-3.4.5.jar:/Users/jiangtao/java/tools/maven_repository/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/spring-jdbc/5.3.6/spring-jdbc-5.3.6.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/spring-beans/5.3.6/spring-beans-5.3.6.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/spring-tx/5.3.6/spring-tx-5.3.6.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/boot/spring-boot-starter-web/2.4.5/spring-boot-starter-web-2.4.5.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/boot/spring-boot-starter-json/2.4.5/spring-boot-starter-json-2.4.5.jar:/Users/jiangtao/java/tools/maven_repository/com/fasterxml/jackson/core/jackson-databind/2.11.4/jackson-databind-2.11.4.jar:/Users/jiangtao/java/tools/maven_repository/com/fasterxml/jackson/core/jackson-annotations/2.11.4/jackson-annotations-2.11.4.jar:/Users/jiangtao/java/tools/maven_repository/com/fasterxml/jackson/core/jackson-core/2.11.4/jackson-core-2.11.4.jar:/Users/jiangtao/java/tools/maven_repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.4/jackson-datatype-jdk8-2.11.4.jar:/Users/jiangtao/java/tools/maven_repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.4/jackson-datatype-jsr310-2.11.4.jar:/Users/jiangtao/java/tools/maven_repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.11.4/jackson-module-parameter-names-2.11.4.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/boot/spring-boot-starter-tomcat/2.4.5/spring-boot-starter-tomcat-2.4.5.jar:/Users/jiangtao/java/tools/maven_repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.45/tomcat-embed-core-9.0.45.jar:/Users/jiangtao/java/tools/maven_repository/org/glassfish/jakarta.el/3.0.3/jakarta.el-3.0.3.jar:/Users/jiangtao/java/tools/maven_repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.45/tomcat-embed-websocket-9.0.45.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/spring-web/5.3.6/spring-web-5.3.6.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/spring-webmvc/5.3.6/spring-webmvc-5.3.6.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/spring-aop/5.3.6/spring-aop-5.3.6.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/spring-context/5.3.6/spring-context-5.3.6.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/spring-expression/5.3.6/spring-expression-5.3.6.jar:/Users/jiangtao/java/tools/maven_repository/org/mybatis/spring/boot/mybatis-spring-boot-starter/2.1.4/mybatis-spring-boot-starter-2.1.4.jar:/Users/jiangtao/java/tools/maven_repository/org/mybatis/spring/boot/mybatis-spring-boot-autoconfigure/2.1.4/mybatis-spring-boot-autoconfigure-2.1.4.jar:/Users/jiangtao/java/tools/maven_repository/org/mybatis/mybatis/3.5.6/mybatis-3.5.6.jar:/Users/jiangtao/java/tools/maven_repository/org/mybatis/mybatis-spring/2.0.6/mybatis-spring-2.0.6.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/spring-core/5.3.6/spring-core-5.3.6.jar:/Users/jiangtao/java/tools/maven_repository/org/springframework/spring-jcl/5.3.6/spring-jcl-5.3.6.jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar com.taojiang.threaddemo.threadlocal.ThreadLocal5
Connected to the target VM, address: '127.0.0.1:60397', transport: 'socket'
main-start
thread1-start...
thread1-start-第一次输出完了...
main-end
thread2-start...
thread2-start-第一次输出完了...
date:2021-02-01
startTime:2020-08-01
startTime:2020-07-01
seq:12
endTime:2021-12-01
endTime:2021-12-01
thread2-end...
thread1-end...
Disconnected from the target VM, address: '127.0.0.1:60397', transport: 'socket'

Process finished with exit code 0

由上可知每个线程可以随便使用localMap变量,随意修改后不会影响别的线程。

五。ThreadLocal不支持继承性
同一个ThrealLocal变量在父线程中被设置了值后,在子线程中是获取不到的
代码实例如下:

public class ThreadLocal6 {

    static ThreadLocal<String> localVar = new ThreadLocal<>();


    public static void main(String[] args) {
        System.out.println("main-start");

        localVar.set("init");

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1-start...");
                System.out.println("thread1设置前:"+localVar.get());
                localVar.set("localVar01");
                System.out.println("thread1设置后:"+localVar.get());
                System.out.println("thread1-end...");
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread2-start...");
                System.out.println("thread2设置前:"+localVar.get());
                localVar.set("localVar02");
                System.out.println("thread2设置后:"+localVar.get());
                System.out.println("thread2-end...");
            }
        });

        thread1.start();
        thread2.start();
        System.out.println("main-getlocalVar:"+localVar.get());

        System.out.println("main-end");
    }
}

运行结果如下:

main-start
thread1-start...
thread1设置前:null
thread1设置后:localVar01
thread1-end...
thread2-start...
thread2设置前:null
thread2设置后:localVar02
thread2-end...
main-getlocalVar:init
main-end

由此可以看出子线程是拿不到父线程事先设置的ThrealLocal变量的值的。所以ThreadLocal不具有继承性。

六。InheritableThreadLocal类
上面说道ThreadLocal类不能继承,那么我们可以做个父传子吗?答案是:可以,使用InheritableThreadLocal类就可以。
具体为什么可以,源码这里不展开说了。
今天先写到这里。写的有点乱,如果有错误欢迎大家指正。

七。ThreadLocal和同步方法解决线程安全问题的区别

学习完本节我们知道了在某些场景下ThrealLocal也可以保证多线程线程安全,那么他跟同步方法有什么区别呢?
同步方法是牺牲了时间,也就是在同一时刻只能有一个线程访问共享变量,所以同步方法是时间换安全的一种方式
而ThrealLocal是每一个线程都有一个共享变量的副本,线程操作的是自己的那份拷贝,所以也解决了线程安全的问题,这种方式是以空间换安全的做法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值