• 并发编程学习笔记(8)----ThreadLocal的使用及源码分析


    1. ThreadLocal的理解

      ThreadLocal,顾名思义,就是线程的本地变量,ThreadLocal会为每个线程创建一个本地变量副本,使得使用ThreadLocal管理的变量在多线程的环境下,每个线程都是访问的是自己内部的副本变量,将全局变量局部化,跟局部变量一样的使用,从而避免了一些线程安全的问题。比如在数据库连接池中获取的连接时,此时的连接对于每个线程来说,都应该是独立的,所以可以使用ThreadLocal来管理该线程的连接。从而避免在多线程环境下的连接的安全问题。

    2. ThreadLocal的使用方式

      ThreaLocal的使用方式也是非常简单的,直接创建一个全局的ThreadLocal,并初始化,这里需要注意的是直接new ThreadLocal时这里ThreadLocal管理的对象是null的,所以如果需要在创建对象的时候就初始化对象的话,可以重写ThreadLocal中的InitialValue方法来进行初始化,初始化完成之后,就是使用了,调用ThreadLocal的get方法拿到被管理的对象,操作对象,再将修改后的对象通过ThreadLocal的set方法保存到ThreadLocal中。代码如下:

    public class ThreadLoclDemo {
    
        private ThreadLocal<Integer> nextc = new ThreadLocal<Integer>(){
            @Override
            protected Integer initialValue() {
                return Integer.valueOf(0);
            }
        };
    
        public int getNext () {
            Integer integer = nextc.get();
            integer++;
            nextc.set(integer);
            return integer;
        }
    
        public static void main(String[] args) {
            final ThreadLoclDemo threadLoclDemo = new ThreadLoclDemo();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        System.out.println(Thread.currentThread().getName() + " " + threadLoclDemo.getNext());
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        System.out.println(Thread.currentThread().getName() + " " + threadLoclDemo.getNext());
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        System.out.println(Thread.currentThread().getName() + " " + threadLoclDemo.getNext());
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        System.out.println(Thread.currentThread().getName() + " " + threadLoclDemo.getNext());
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }

      这里通过五个线程去执行getNext()方法,输出如下:

    Thread-0 1
    Thread-1 1
    Thread-2 1
    Thread-3 1
    Thread-1 2
    Thread-2 2
    Thread-0 2
    Thread-3 2
    Thread-1 3
    Thread-0 3
    Thread-2 3
    Thread-2 4
    Thread-1 4
    Thread-0 4
    Thread-3 3
    Thread-0 5
    Thread-2 5
    Thread-1 5
    Thread-2 6
    Thread-1 6
    Thread-0 6
    Thread-3 4
    Thread-2 7
    Thread-0 7
    Thread-1 7
    Thread-1 8

      可以看出,对于每个线程,它们锁获取到的值都是自增1之后的值,并没有出现线程安全问题或是线程之间由于操作共享数据而相互影响的情况,输出犹如线程在使用局部变量一样清晰,这就是ThreadLocal的使用方式和作用。

     3.ThreadLocal的源码分析

      ThreadLocal的源码还是比较简单的,它的代码之中我们只需要关注get()/set()/remove()三个方法即可

      首先看set()方法:

    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }

      首先获取当前线程,通过getMap()传入当前线程去获取ThreadLocalMap,ThreadLocalMap属于Thread的一个内部属性threadLocals,所以通过当前线程就可以获取当前线程所对应的ThreadLocalMap,如果当前map不为空,表示当前线程已经被创建,则以当前线程为key,修改value为值,保存变量,如果为空,则通过createMap()新建一个ThreadLocalMap对象,并将其赋值给当前Thread对象的threadLocals属性。这就完成了set()的操作。

      get()方法:

    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value;
            }
            return setInitialValue();
        }

      获取当前线程,通过当前线程获取ThreadLocalMap,如果map为空,则返回setInitialValue()方法,这里此方法返回的是null,所以在最开始初始化时,被管理的对象的值为null也是这个原因,可以重写initialValue()方法来进行初始化。如果map不为空,则通过map中的getEntry()方法获取ThreadLocalMap中的entry对象,所以ThreadLocal管理的对象实际上时存储在ThreadLocalMap的Entry内部类中的value中的,如果entry存在,直接返回e.value,也就是ThreadLocal管理的对象,否则返回初始化的值(null)。

      remove()方法:

     public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }

      remove()方法很简单,获取当前线程的ThreadLocalMap,不为空则调用ThreadLocalMap的remove()方法。直接移除当前ThreadLocalMap中的Entry对象,即移除了当前线程和ThreadLocal为当前线程创建的内部副本。

      ThreadLocal的分享就到这里,不足之处,请各位多多指教。

  • 相关阅读:
    no,no,不要使用kill -9
    Linux中etc目录详解
    Quartz任务调度器的使用
    RMI(Remote Method invocation,远程方法访问)
    SilverLight扩展控件RadTreeView
    SiverLight和HTML交互
    SilverLight控件之ContextMenu和RadContextMenu(菜单)
    SilverLight之向后台请求数据-WebClient
    SilverLight控件样式及控件模版
    在SilverLight中代码编写可选择树节点
  • 原文地址:https://www.cnblogs.com/Eternally-dream/p/9724591.html
Copyright © 2020-2023  润新知