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-企业级开发》

posted @ 2017-04-18 22:03  soar_hu  阅读(157)  评论(0编辑  收藏  举报