zoukankan      html  css  js  c++  java
  • java并发:线程同步机制之ThreadLocal

    1.简述ThreadLocal

      ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。ThreadLocal是一个线程级别的局部变量,下面是线程局部变量(ThreadLocal variables)的关键点:

      A、当使用ThreadLocal维护变量时,若多个线程访问ThreadLocal实例,ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。

      B、从线程的角度看,目标变量就像是线程的本地变量,这也是类名中Local所要表达的意思。

    2.细看ThreadLocal

    ThreadLocal<T>类很简单,只有四个方法:

    (1)void set(T value),该方法用来设置当前线程中变量的副本

    (2)public T get(),该方法是用来获取ThreadLocal在当前线程中保存的变量副本

    (3)public void remove(),该方法用来移除当前线程中变量的副本,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束以后,对应线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

    (4)protected T initialValue(),该方法是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,ThreadLocal中的缺省实现直接返回一个null。

    3.ThreadLocal示例

    简单的使用方法如下:

    package com.test;
    
    public class ThreadMain {
        // ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
        private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
            public Integer initialValue() {
                return 0;
            }
        };
    
        // ②获取下一个序列值
        public int getNextNum() {
            seqNum.set(seqNum.get() + 1);
            return seqNum.get();
        }
    
        public static void main(String[] args) {
            ThreadMain sn = new ThreadMain();
            // ③ 3个线程共享sn,各自产生序列号
            TestClient t1 = new TestClient(sn);
            TestClient t2 = new TestClient(sn);
            TestClient t3 = new TestClient(sn);
            t1.start();
            t2.start();
            t3.start();
        }
    
        private static class TestClient extends Thread {
            private ThreadMain sn;
    
            public TestClient(ThreadMain sn) {
                this.sn = sn;
            }
    
            public void run() {
                for (int i = 0; i < 3; i++) {
                    // ④每个线程打出3个序列值
                    System.out.println("thread[" + Thread.currentThread().getName()
                            + "] --> sn[" + sn.getNextNum() + "]");
                }
            }
        }
        
    }

    结果如下:

    thread[Thread-0] --> sn[1]
    thread[Thread-2] --> sn[1]
    thread[Thread-1] --> sn[1]
    thread[Thread-2] --> sn[2]
    thread[Thread-0] --> sn[2]
    thread[Thread-2] --> sn[3]
    thread[Thread-1] --> sn[2]
    thread[Thread-1] --> sn[3]
    thread[Thread-0] --> sn[3]

    另一个案例

    package com.csu.thread;
    
    class GlobalVarManager {
        private static ThreadLocal<String> globalVars = new ThreadLocal<String>(){
            protected String initialValue() {
                return "hello";
            }
        };
        
        public static ThreadLocal<String> getglobalVars() {
            return globalVars;
        }
    }
    
    class ThreadRun implements Runnable {
        
        private ThreadLocal<String> t;
        private String str;
        
        ThreadRun(ThreadLocal<String> temp, String s) {
            this.t = temp;
            this.str = s;
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread()+"改变前:" + t.get());
            t.set(str);
            System.out.println(Thread.currentThread()+"改变后:" + t.get());
        }
    }
    
    public class ThreadLocalTry {
    
        public static void main(String[] args) {
            for (int i =1; i < 5; i++) {
                new Thread(new ThreadRun(GlobalVarManager.getglobalVars(), ""+i)).start();
            }
        }
    }

    结果如下:

    Thread[Thread-0,5,main]改变前:hello
    Thread[Thread-1,5,main]改变前:hello
    Thread[Thread-0,5,main]改变后:1
    Thread[Thread-2,5,main]改变前:hello
    Thread[Thread-1,5,main]改变后:2
    Thread[Thread-2,5,main]改变后:3
    Thread[Thread-3,5,main]改变前:hello
    Thread[Thread-3,5,main]改变后:4

     上述案例也可按如下方式来实现:

    package com.csu.test;
    
    class GlobalVarManager {
        private static ThreadLocal<String> globalVars = new ThreadLocal<String>(){
            protected String initialValue() {
                return "hello";
            }
        };
    
        public static String getGlobalVars() {
            return globalVars.get();
        }
    
        public static void setGlobalVars(String str) {
            globalVars.set(str);
        }
    }
    
    class ThreadRun implements Runnable {
    
        private String str = null;
    
        public ThreadRun(String temp) {
            str = temp;
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread()+"改变前:" + GlobalVarManager.getGlobalVars());
            GlobalVarManager.setGlobalVars(str);
            System.out.println(Thread.currentThread()+"改变后:" + GlobalVarManager.getGlobalVars());
        }
    }
    
    public class ThreadLocalTest {
    
        public static void main(String[] args) {
            for (int i = 1; i < 5; i++) {
                new Thread(new ThreadRun("" + i)).start();
            }
        }
    }

    结果如下:

    Thread[Thread-3,5,main]改变前:hello
    Thread[Thread-2,5,main]改变前:hello
    Thread[Thread-1,5,main]改变前:hello
    Thread[Thread-0,5,main]改变前:hello
    Thread[Thread-1,5,main]改变后:2
    Thread[Thread-2,5,main]改变后:3
    Thread[Thread-3,5,main]改变后:4
    Thread[Thread-0,5,main]改变后:1

    4.ThreadLocal的实现机制

    此部分内容暂没有深入研究,欲了解更多内容请参考https://www.cnblogs.com/dennyzhangdd/p/7978455.html

    (1)get()方法源码如下:

    (2)set()方法源码如下:

    (3)remove()方法源码如下:

    (4)上述几个函数涉及到如下两个函数

    从前述源码可以看出,ThreadLocal的get、set、remove方法都是操作当前线程,而从Thread的源码可以看出该类有一个ThreadLocal.ThreadLocalMap类型的变量threadLocals,该变量在初次调用ThreadLocal的set()方法时通过createMap()方法初始化

    5.ThreadLocalMap

    ThreadLocalMap的部分源码如下:

        /**
         * ThreadLocalMap is a customized hash map suitable only for
         * maintaining thread local values. No operations are exported
         * outside of the ThreadLocal class. The class is package private to
         * allow declaration of fields in class Thread.  To help deal with
         * very large and long-lived usages, the hash table entries use
         * WeakReferences for keys. However, since reference queues are not
         * used, stale entries are guaranteed to be removed only when
         * the table starts running out of space.
         */
        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;
                }
            }
    
            /**
             * The initial capacity -- MUST be a power of two.
             */
            private static final int INITIAL_CAPACITY = 16;
    
            /**
             * The table, resized as necessary.
             * table.length MUST always be a power of two.
             */
            private Entry[] table;
    
            /**
             * The number of entries in the table.
             */
            private int size = 0;
    
            /**
             * The next size value at which to resize.
             */
            private int threshold; // Default to 0
    
            /**
             * Set the resize threshold to maintain at worst a 2/3 load factor.
             */
            private void setThreshold(int len) {
                threshold = len * 2 / 3;
            }
    
            /**
             * Increment i modulo len.
             */
            private static int nextIndex(int i, int len) {
                return ((i + 1 < len) ? i + 1 : 0);
            }
    
            /**
             * Decrement i modulo len.
             */
            private static int prevIndex(int i, int len) {
                return ((i - 1 >= 0) ? i - 1 : len - 1);
            }
    
            /**
             * Construct a new map initially containing (firstKey, firstValue).
             * ThreadLocalMaps are constructed lazily, so we only create
             * one when we have at least one entry to put in it.
             */
            ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                table = new Entry[INITIAL_CAPACITY];
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                setThreshold(INITIAL_CAPACITY);
            }
    
            /**
             * Construct a new map including all Inheritable ThreadLocals
             * from given parent map. Called only by createInheritedMap.
             *
             * @param parentMap the map associated with parent thread.
             */
            private ThreadLocalMap(ThreadLocalMap parentMap) {
                Entry[] parentTable = parentMap.table;
                int len = parentTable.length;
                setThreshold(len);
                table = new Entry[len];
    
                for (int j = 0; j < len; j++) {
                    Entry e = parentTable[j];
                    if (e != null) {
                        @SuppressWarnings("unchecked")
                        ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                        if (key != null) {
                            Object value = key.childValue(e.value);
                            Entry c = new Entry(key, value);
                            int h = key.threadLocalHashCode & (len - 1);
                            while (table[h] != null)
                                h = nextIndex(h, len);
                            table[h] = c;
                            size++;
                        }
                    }
                }
            }

    此处重点关注一下ThreadLocalMap中的几个成员变量及方法

    (1)private Entry[] table;

    table是一个Entry类型的数组,该变量在ThreadLocalMap的构造函数中初始化

    Entry是ThreadLocalMap的一个内部类

    (2)set()方法

            /**
             * Set the value associated with key.
             *
             * @param key the thread local object
             * @param value the value to be set
             */
            private void set(ThreadLocal<?> key, Object value) {
    
                // We don't use a fast path as with get() because it is at
                // least as common to use set() to create new entries as
                // it is to replace existing ones, in which case, a fast
                // path would fail more often than not.
    
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
    
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
    
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }

    (3)getEntry()方法

            /**
             * Get the entry associated with key.  This method
             * itself handles only the fast path: a direct hit of existing
             * key. It otherwise relays to getEntryAfterMiss.  This is
             * designed to maximize performance for direct hits, in part
             * by making this method readily inlinable.
             *
             * @param  key the thread local object
             * @return the entry associated with key, or null if no such
             */
            private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                if (e != null && e.get() == key)
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);
            }
    
            /**
             * Version of getEntry method for use when key is not found in
             * its direct hash slot.
             *
             * @param  key the thread local object
             * @param  i the table index for key's hash code
             * @param  e the entry at table[i]
             * @return the entry associated with key, or null if no such
             */
            private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
                Entry[] tab = table;
                int len = tab.length;
    
                while (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == key)
                        return e;
                    if (k == null)
                        expungeStaleEntry(i);
                    else
                        i = nextIndex(i, len);
                    e = tab[i];
                }
                return null;
            }

    (4)remove()方法

            /**
             * Remove the entry for key.
             */
            private void remove(ThreadLocal<?> key) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    if (e.get() == key) {
                        e.clear();
                        expungeStaleEntry(i);
                        return;
                    }
                }
            }

    6.总结

    ThreadLocal一般都是声明在静态变量中,如果不断地创建ThreadLocal而没有调用其remove方法,将导致内存泄露,特别是在高并发的Web容器当中。

    ThreadLocal在处理线程的局部变量时比synchronized同步机制解决线程安全问题更简单,更方便,且程序拥有更高的并发性。

  • 相关阅读:
    OpenCV——IplImage
    OpenCV——常用函数查询
    OpenCV——Sobel和拉普拉斯变换
    OpenCV——CvSeq动态结构序列
    OpenCV——人脸检测
    Java—Integer类
    Java—NumberFormat与DecimalFormat类
    sql事务机制
    存储过程
    java中的抽象类和接口
  • 原文地址:https://www.cnblogs.com/studyLog-share/p/5295557.html
Copyright © 2011-2022 走看看