Java多线程之ThreadLocal

来源:互联网 发布:重启后正在准备windows 编辑:程序博客网 时间:2024/05/17 07:47
ThreadLocal直译为"本地线程",但它并不是这样。ThreadLocal本身其实是一个容器,用于 存放线程的局部变量,这个类能使线程中的某个值与保存值的线程对象关联起来。ThreadLocal提供了get和set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份副本,因此get总是返回当前执行线程在调用set时设置的最新值。下面将以实例代码说明。场景:一个序列号生成器的程序同时会有多个线程访问它,要保证每个线程得到的序列号都是自增的,而相互不干扰。先定义一个接口:
public interface Sequence {    int getNumber();}
每次调用getNumber()方法可获取序列号,下次在调用时,序列号会自增。

线程类

public class ClientThread implements Runnable {    private Sequence sequence;    public ClientThread(Sequence sequence) {        this.sequence = sequence;    }    @Override    public void run() {        for (int i = 0; i < 3; ++i) {            System.out.println(Thread.currentThread().getName() + " => " + sequence.getNumber());        }    }}

错误的案例

public class SequenceA implements Sequence {    private static int number = 0;    @Override    public int getNumber() {        number++;        return number;    }    public static void main(String[] args) {        Sequence sequence = new SequenceA();        ClientThread clientThread = new ClientThread(sequence);        ClientThread clientThread1 = new ClientThread(sequence);        ClientThread clientThread2 = new ClientThread(sequence);        new Thread(clientThread).start();        new Thread(clientThread1).start();        new Thread(clientThread2).start();    }}
运行结果:Thread-0 => 1Thread-0 => 2Thread-0 => 3Thread-1 => 4Thread-1 => 5Thread-1 => 6Thread-2 => 7Thread-2 => 8Thread-2 => 9因为number是static修饰,属于共享资源,所以number一直自增,不能保证线程安全。

使用ThreadLocal

public class SequenceB implements Sequence {    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){        @Override        protected Integer initialValue() {            return 0;        }    };    @Override    public int getNumber() {        threadLocal.set(threadLocal.get() + 1);        return threadLocal.get();    }    public static void main(String[] args) {        Sequence sequence = new SequenceB();        ClientThread clientThread = new ClientThread(sequence);        ClientThread clientThread1 = new ClientThread(sequence);        ClientThread clientThread2 = new ClientThread(sequence);        new Thread(clientThread).start();        new Thread(clientThread1).start();        new Thread(clientThread2).start();    }}
运行结果Thread-0 => 1Thread-1 => 1Thread-1 => 2Thread-1 => 3Thread-2 => 1Thread-2 => 2Thread-2 => 3Thread-0 => 2Thread-0 => 3从运行结果可以看出,每个线程相互独立了,同样是static变量,对于不同的线程而言,它没有被共享,而是每个线程各一份,这样就保证了线程安全。也就是说,ThreadLocal为每个线程提供了独立的副本。ThreadLocal的APIpublic void set(T value):将值放入线程局部变量中;public T get():从线程局部变量中获取值;public void remove():从线程局部变量中移除值;protected T initialValue():返回线程局部变量中的初始值(默认为null)

自己实现ThreadLocal

ThreadLocal其实就是封装了一个线程安全的Map
public class MyThreadLocal<T> {    //封装一个线程安全的Map,以线程对象为key,线程变量T为value    private Map<Thread, T> threadTMap = Collections.synchronizedMap(new HashMap<Thread, T>());    public void set(T t) {        threadTMap.put(Thread.currentThread(), t);    }    public T get() {        Thread thread = Thread.currentThread();        T value = threadTMap.get(thread);        if (value == null && !threadTMap.containsKey(thread)) {            value = initValue();            threadTMap.put(thread, value);        }        return value;    }    public void remove() {        threadTMap.remove(Thread.currentThread());    }    protected T initValue() {        return null;    }}

使用MyThreadLocal来实现:

public class SequenceC implements Sequence {    private static MyThreadLocal<Integer> myThreadLocal = new MyThreadLocal<Integer>(){        @Override        protected Integer initValue() {            return 0;        }    };    @Override    public int getNumber() {        myThreadLocal.set(myThreadLocal.get() + 1);        return myThreadLocal.get();    }    public static void main(String[] args) {        Sequence sequenceC = new SequenceC();        new Thread(new ClientThread(sequenceC)).start();        new Thread(new ClientThread(sequenceC)).start();        new Thread(new ClientThread(sequenceC)).start();    }}
运行结果:Thread-0 => 1Thread-0 => 2Thread-0 => 3Thread-1 => 1Thread-1 => 2Thread-1 => 3Thread-2 => 1Thread-2 => 2Thread-2 => 3运行效果和之前一样,同样正确。ThreadLocal对象通常用于防止对可变的单实例变量或全局变量进行共享。例如:在单线程应用程序中可能维持一个全局的数据库连接,并在程序启动时初始化这个连接,从而避免在调用每个方法时都要传递一个Connection对象。由于JDBC的连接对象不一定是线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的。通过将JDBC的连接保存到ThreadLocal对象中,每个线程都会有属于自己的来连接。
原创粉丝点击