zoukankan      html  css  js  c++  java
  • java多线程基本概述(八)——ThreadLocal

    ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。再spring框架中大量使用。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。

    api:

    public class ThreadLocal<T>
    extends Object
    This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 
    ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). For example, the class below generates unique identifiers local to each thread. A thread's id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls. import java.util.concurrent.atomic.AtomicInteger; public class ThreadId { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } } Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

    示例代码:

    一个线程安全的方法:

    package soarhu;
    
    class TestNum {
        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 class Test{
        public static void main(String[] args)  {
            TestNum t = new TestNum();
            for (int i = 0; i < 3; i++) {
                new Thread(){
                    @Override
                    public void run() {
                        for (int j = 0; j < 2; j++) {
                            System.out.println(t.getNextNum()+"<>"+Thread.currentThread().getName());
                        }
    
                    }
                }.start();
            }
        }
    }

    输出结果:

    1<>Thread-1
    2<>Thread-1
    1<>Thread-2
    2<>Thread-2
    1<>Thread-0
    2<>Thread-0
    
    Process finished with exit code 0

    可以看到开了3个线程,但三个线程的值都没有被影响到。

    package soarhu;
    
    import java.util.concurrent.TimeUnit;
    
    class Hodle{
        public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    }
    
     class ThreadA extends Thread {
    
        @Override
        public void run() {
            try {
                for (int i = 0; i < 20; i++) {
                    Hodle.threadLocal.set("a"+(i+1));
                    System.out.println("thread a getValue: "+Hodle.threadLocal.get());
                    TimeUnit.MILLISECONDS.sleep(2);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
     class ThreadB extends Thread {
        @Override
         public void run() {
            try {
                for (int i = 0; i < 20; i++) {
                    Hodle.threadLocal.set("b"+(i+1));
                    System.out.println("thread b getValue: "+Hodle.threadLocal.get());
                    TimeUnit.MILLISECONDS.sleep(2);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    
    public class Test {
        public static void main(String[] args)  {
    
            try {
                ThreadB b = new ThreadB();
                ThreadA a = new ThreadA();
                a.start();
                b.start();
                for (int i = 0; i < 20; i++) {
                    Hodle.threadLocal.set("m"+(i+1));
                    System.out.println("main getValue: "+Hodle.threadLocal.get());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    输出结果:

    main getValue: m1
    main getValue: m2
    main getValue: m3
    main getValue: m4
    main getValue: m5
    main getValue: m6
    main getValue: m7
    main getValue: m8
    main getValue: m9
    main getValue: m10
    main getValue: m11
    main getValue: m12
    main getValue: m13
    main getValue: m14
    main getValue: m15
    main getValue: m16
    main getValue: m17
    main getValue: m18
    main getValue: m19
    main getValue: m20
    thread a getValue: a1
    thread b getValue: b1
    thread b getValue: b2
    thread a getValue: a2
    thread a getValue: a3
    thread b getValue: b3
    thread a getValue: a4
    thread b getValue: b4
    thread a getValue: a5
    thread b getValue: b5
    thread b getValue: b6
    thread a getValue: a6
    thread b getValue: b7
    thread a getValue: a7
    thread b getValue: b8
    thread a getValue: a8
    thread b getValue: b9
    thread a getValue: a9
    thread b getValue: b10
    thread a getValue: a10
    thread a getValue: a11
    thread b getValue: b11
    thread a getValue: a12
    thread b getValue: b12
    thread b getValue: b13
    thread a getValue: a13
    thread b getValue: b14
    thread a getValue: a14
    thread b getValue: b15
    thread a getValue: a15
    thread a getValue: a16
    thread b getValue: b16
    thread a getValue: a17
    thread b getValue: b17
    thread b getValue: b18
    thread a getValue: a18
    thread b getValue: b19
    thread a getValue: a19
    thread b getValue: b20
    thread a getValue: a20
    
    Process finished with exit code 0

    可知每个线程都维护着自己的变量值。内部其实以thread为键的一个map.

    查看set/get源码

     public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
    
    -------------------------------------------------
      public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }

     ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

      在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

      而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

      概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

      spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

      一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,

    通通透透理解ThreadLocal

      同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。

        例子:非线程安全

    package com.test;  
      
    import java.sql.Connection;  
    import java.sql.SQLException;  
    import java.sql.Statement;  
      
    public class TestDao {  
        private Connection conn;// ①一个非线程安全的变量  
      
        public void addTopic() throws SQLException {  
            Statement stat = conn.createStatement();// ②引用非线程安全变量  
            //
        }  
    } 

    由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:

    代码清单4 TestDao:线程安全

    package com.test;  
      
    import java.sql.Connection;  
    import java.sql.SQLException;  
    import java.sql.Statement;  
      
    public class TestDaoNew {  
        // ①使用ThreadLocal保存Connection变量  
        private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();  
      
        public static Connection getConnection() {  
            // ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,  
            // 并将其保存到线程本地变量中。  
            if (connThreadLocal.get() == null) {  
                Connection conn = getConnection();  
                connThreadLocal.set(conn);  
                return conn;  
            } else {  
                return connThreadLocal.get();// ③直接返回线程本地变量  
            }  
        }  
      
        public void addTopic() throws SQLException {  
            // ④从ThreadLocal中获取线程对应的Connection  
            Statement stat = getConnection().createStatement();  
        }  
    }  

    不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。

      当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。

    package com.test;  
      
    import java.sql.Connection;  
    import java.sql.DriverManager;  
    import java.sql.SQLException;  
      
    public class ConnectionManager {  
      
        private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
            @Override  
            protected Connection initialValue() {  
                Connection conn = null;  
                try {  
                    conn = DriverManager.getConnection(  
                            "jdbc:mysql://localhost:3306/test", "username",  
                            "password");  
                } catch (SQLException e) {  
                    e.printStackTrace();  
                }  
                return conn;  
            }  
        };  
      
        public static Connection getConnection() {  
            return connectionHolder.get();  
        }  
      
        public static void setConnection(Connection conn) {  
            connectionHolder.set(conn);  
        }  
    }  

    参考书籍《spring4.x-企业级开发》

  • 相关阅读:
    用textfield制作richTextEdit
    鼠标手势替换闪烁不停地问题
    怎样在FlexBuilder中使用FlashCS中的组件[翻译]
    一个可以减少代码输入量的用法
    使用Flex library project
    初级程序员进步的几个标志
    给flashBuilder整几个快捷键
    在纯AS工程中嵌入个别字体方法
    软件设计中应重视的非技术因素
    ASP.NET MVC 2.0 Html.RenderPartial & Html.RenderAction
  • 原文地址:https://www.cnblogs.com/soar-hu/p/6730794.html
Copyright © 2011-2022 走看看