ThreadLocal

117 阅读6分钟

1. ScheduledExecutorService比Timer更安全,功能更强大

  1. Timer不支持多线程。全部挂在Timer下的任务都是单线程的,任务仅仅能串行运行。假设当中一个任务运行时间过长。会影响到其它任务的运行,然后就可能会有各种接踵而来的问题。

2.Timer的线程不捕获异常。TimerTask假设抛出异常,那么Timer唯一的进程就会挂掉,这样挂在Timer下的全部任务都会无法继续运行。

2. shotdown() showdownNow()区别

可以关闭 ExecutorService,这将导致其拒绝新任务。提供两个方法来关闭 ExecutorService。 

shutdown() 方法在终止前允许执行以前提交的任务, 

shutdownNow() 方法阻止等待任务启动并试图停止当前正在执行的任务。在终止时执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。关闭未使用的 ExecutorService 以允许回收其资源。 

一般分两个阶段关闭 ExecutorService。第一阶段调用 shutdown 拒绝传入任务,然后调用 shutdownNow(如有必要)取消所有遗留的任务

在 线程池中使用 threadLocal的时候会有问题,产生问题的原因是因为 线程池会利用原来的线程,但是ThreadLocal是 与线程相关的

所以如果想在ThreadPoolExecutor 线程池中使用ThreadLocal的话,需要 继承ThreadPoolExecutor 类,重写以下两个方法,在线程执行完成的时候,重置ThreadLocal中的数据

3. ThreadLocal

使用ThreadLocal 实现保存登录用户信息 自定义Interceptor实现HandlerInteceptor,在preHandle方法中实现set,在 afterCompletion()方法中执行remove操作,这样就可以将登录信息保存在JVM中并且不需要再从redis中获取

Thradlocal使用后要调用remove()方法,否则容易产生内存泄漏

@Override  
    protected void beforeExecute(Thread t, Runnable r) {  
                //任务执行回调可以作为重置操作  
        MyThreadLocal.currentAgentId.set(888);       
        }  
      
    protected void afterExecute(Runnable r, Throwable t) {  
                //任务执行回调可以作为重置操作  
        MyThreadLocal.currentAgentId.set(null);  
    }  

线程安全问题的产生是因为,

有些变量 数据 是多线程共享的,不同的线程操作同一个变量时,可能导致其他的线程获取到的数据不准确,所以才会有线程安全问题.

servlet 为单例的,所以 servlet类中不可以有实例变量,因为所有的线程都使用同一个servlet实例.如果此实例有实例属性的时候,就容易出现线程安全问题.

struts2 中的action类是 多实例模式,即每一个请求都会创建一个单独的action实例去处理.所以action类中可以有实例属性,用来接收表单提交的数据及方法返回的数据.

springmvc 中的controller 默认都是singleton即单例的,所以controller中也不可以有多线程操作的实例属性.

synchronized是 以时间换空间.即 将可能并发操作的代码块或者方法加上锁,保证同一个时间只有一个线程可以访问此段代码,只有等此段代码执行完成后,其他的线程才能进入.如果此时有5个线程,则只有一个线程能访问,其他4个线程在等待队列中等待执行.

ThreadLocal 是以空间换时间,即它将并发访问的 变量 为访问的的所有线程都拷贝一份备份,相当于每个线程操作的都是自己线程可访问的,所以有5个线程访问的时候,此变量就会有5份,保存到 Thead 类中的 ThreadLocal.ThreadLocalMap对象中 此类似Map的对象以ThreadLocal对象为key ,以变量值作为value. 由于 ThreadLocalMap对象是属于Thread(线程类的),所以每个线程都有自己的拷贝变量,所以避免了并发问题.

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

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

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

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

那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:


   public void set(T value) {  
         Thread t = Thread.currentThread();  
         ThreadLocalMap map = getMap(t);  
         if (map != null)  
             map.set(this, value);  
         else  
             createMap(t, value);  
     }  

在这个方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。

线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。

  进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。