在多线程场景中,有一个常用的类ThreadLocal,该类作为一个桥梁可以将线程要操作的全局变量和线程本身绑定:
如下多线程测试程序:
package test.HimmaQ; class Channel { //消息发送通道 private static Message message; private Channel(){}; public static void setMessage(Message m){ message = m; } public static void send(){//发送消息 System.out.println(message.getInfo()); } } class Message { private String info; public void setInfo(String info) { this.info = info; } public String getInfo(){ return info; } } public class threadLocalTest { public static void main(String[] args) { new Thread(()->{ Message msg = new Message(); msg.setInfo("第一个线程发送消息"); Channel.setMessage(msg); Channel.send(); }).start(); new Thread(()->{ Message msg = new Message(); msg.setInfo("第二个线程发送消息"); Channel.setMessage(msg); Channel.send(); }).start(); new Thread(()->{ Message msg = new Message(); msg.setInfo("第三个线程发送消息"); Channel.setMessage(msg); Channel.send(); }).start(); } }
运行结果:
第三个线程发送消息
第三个线程发送消息
第三个线程发送消息
Process finished with exit code 0
可以看到,连续3个线程执行输出,输出结果与预期结果不符;
原因分析:对消息发送通道Channel类,其属性message是静态全局的,因此每一个线程过来都会操作该变量,当第一个线程为该对象设置了值还没有发出时,第二个线程又重新给它设置了值,同样的,第三个线程会覆盖前两个线程设置的值,因此在电脑速度快的情况下,我们只看到了第三个线程的输出结果。即,多线程对全局变量操作的情况下,发生了线程不安全(全局变量的值的覆盖)。
ThreadLoacl类:
public class ThreadLocal<T>
extends Object
返回值 | 方法 | 说明 |
T | get() | 返回当前线程本地变量的当前线程的副本中的值。 |
void | remove() |
移除此线程局部变量的当前线程的值。
|
void | set(T value) | 将此线程局部变量的当前线程的副本设置为指定的值。 |
该类提供线程局部变量。通过其get和set方法,可以将线程要操作的全局变量的值与当前线程绑定。ThreadLocal实例通常是私有的静态字段,用在需要关联线程的时候,比如将用户id和当前线程绑定时候,可以将ID设置为ThreadLocal类。
可以将ThreadLocal类变量理解为一个map,其中存放的是每个线程与其对应的全局变量副本的键值对,在线程使用的时候,拿到的并不是全局变量本身,而是线程对应的全局变量的副本,这样就实现了线程和变量的绑定。
解决方案:
在Channel类中引入私有的全局ThreadLocal类变量,用来将线程和需要操作的message关联起来:
package test.HimmaQ; class Channel { //消息发送通道 private static final ThreadLocal<Message> THREADLOCAL = new ThreadLocal<Message>(); private Channel(){}; public static void setMessage(Message m){ THREADLOCAL.set(m); } public static void send(){//发送消息 System.out.println(THREADLOCAL.get().getInfo()); } } class Message { private String info; public void setInfo(String info) { this.info = info; } public String getInfo(){ return info; } } public class threadLocalTest { public static void main(String[] args) { new Thread(()->{ Message msg = new Message(); msg.setInfo("第一个线程发送消息"); Channel.setMessage(msg); Channel.send(); }).start(); new Thread(()->{ Message msg = new Message(); msg.setInfo("第二个线程发送消息"); Channel.setMessage(msg); Channel.send(); }).start(); new Thread(()->{ Message msg = new Message(); msg.setInfo("第三个线程发送消息"); Channel.setMessage(msg); Channel.send(); }).start(); } }
运行结果:
第一个线程发送消息
第二个线程发送消息
第三个线程发送消息
Process finished with exit code 0