Java多线程之ThreadLocal

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 => 1
Thread-0 => 2
Thread-0 => 3
Thread-1 => 4
Thread-1 => 5
Thread-1 => 6
Thread-2 => 7
Thread-2 => 8
Thread-2 => 9
因为number是static修饰,属于共享资源,所以number一直自增,不能保证线程安全。

使用ThreadLocal

public class SequenceB implements Sequence {
    private static ThreadLocal threadLocal = new ThreadLocal(){
        @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 => 1
Thread-1 => 1
Thread-1 => 2
Thread-1 => 3
Thread-2 => 1
Thread-2 => 2
Thread-2 => 3
Thread-0 => 2
Thread-0 => 3

从运行结果可以看出,每个线程相互独立了,同样是static变量,对于不同的线程而言,它没有被共享,而是每个线程各一份,这样就保证了线程安全。也就是说,ThreadLocal为每个线程提供了独立的副本。

ThreadLocal的API
public void set(T value):将值放入线程局部变量中;
public T get():从线程局部变量中获取值;
public void remove():从线程局部变量中移除值;
protected T initialValue():返回线程局部变量中的初始值(默认为null)

自己实现ThreadLocal

ThreadLocal其实就是封装了一个线程安全的Map
public class MyThreadLocal {
    //封装一个线程安全的Map,以线程对象为key,线程变量T为value
    private Map threadTMap = Collections.synchronizedMap(new HashMap());

    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 myThreadLocal = new MyThreadLocal(){
        @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 => 1
Thread-0 => 2
Thread-0 => 3
Thread-1 => 1
Thread-1 => 2
Thread-1 => 3
Thread-2 => 1
Thread-2 => 2
Thread-2 => 3
运行效果和之前一样,同样正确。

ThreadLocal对象通常用于防止对可变的单实例变量或全局变量进行共享。例如:在单线程应用程序中可能维持一个全局的数据库连接,并在程序启动时初始化这个连接,从而避免在调用每个方法时都要传递一个Connection对象。由于JDBC的连接对象不一定是线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的。通过将JDBC的连接保存到ThreadLocal对象中,每个线程都会有属于自己的来连接。

你可能感兴趣的:(java,多线程,Java,多线程)