`

ThreadLocal与线程安全

阅读更多

首先来看一下线程安全问题产生的两个前提条件: 

1.数据共享,多个线程访问同样的数据。 

2.共享数据是可变的,多个线程对访问的共享数据作出了修改。 

 

实例:

        定义一个共享数据:

public static int a = 0;

        多线程对该共享数据进行修改: 

private static void plus() {
    for (int i = 0; i < 10; i++) {
        new Thread() {
            public void run() {
                a++;
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("plus:" + Thread.currentThread().getName() + ": " + a);
            }
        }.start();
    }
}

        运行结果:

plus:Thread-5: 5
plus:Thread-2: 5
plus:Thread-6: 5
plus:Thread-9: 5
plus:Thread-1: 6
plus:Thread-0: 8
plus:Thread-4: 8
plus:Thread-3: 10
plus:Thread-7: 10
plus:Thread-8: 10

        很明显,在第一次输出a的值的时候,a的值就已经被其他线程修改到5了,显然线程不安全。 

        利用synchronized关键字将修改a值的地方和输出的地方上锁,让这段代码在某一个时间段内始终只有一个线程在执行:

private static void plus() {
    for (int i = 0; i < 10; i++) {
        new Thread() {
            public void run() {
                synchronized (JavaVariable.class) {
                    a++;
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("plus:" + Thread.currentThread().getName() + ": " + a);
                }
            }
        }.start();
    }
}

        运行结果:

plus:Thread-2: 1
plus:Thread-6: 2
plus:Thread-7: 3
plus:Thread-3: 4
plus:Thread-8: 5
plus:Thread-4: 6
plus:Thread-0: 7
plus:Thread-9: 8
plus:Thread-5: 9
plus:Thread-1: 10

        那么,如果ThreadLocal是为了解决线程安全设计的,请同样用ThreadLocal解决上面的问题。即我需要多个线程共同修改一个值,但是要保持这个值递增,就是说后面的线程不能在前一个线程还没有输出就又去修改。 

尝试如下:

//定义一个ThreadLocal
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
    protected Integer initialValue() {
        return 0;
    }
};

private static void plus() throws Exception {
    for (int i = 0; i < 10; i++) {
        new Thread() {
            public void run() {
                //1
                a = threadLocal.get();
                a++;
                
                //2
                threadLocal.set(a);
                System.out.println("plus:" + Thread.currentThread().getName() + ": " + threadLocal.get());
            }
        }.start();
    }
}

        运行结果:全部输出1。

        很明显,在代码“1”的时候,每一个线程都会将threadLocal的初始值0赋值给共享变量a,因为每一个线程从threadLocal.get()拿到的值都是自己threadLocal保存的。所以对于共享变量a来讲,每个线程都会首先将自己threadLocal里面的初始值0赋值给a,然后将共享变量a+1,然后将a+1的值设置到自己的ThreadLocalMap中,其他线程就访问不到了。下一个线程来的时候又会将自己threadLocal里面的初始值0赋值给a,然后将 a+1,然后... 如此周而复始。a只是被在0和1之间改来改去,最终放到每一个线程的threadLocal里面的a+1的值就不再共享。

        对于a这个共享变量来讲,如果在for循环创建的某线程A即将执行代码“2”之前,被其他for循环以外的某个线程改了一把。那么存到线程A的ThreadLocalMap中的值就是被改过的值了。 

        综上所述,Java的ThreadLocal不是设计用来解决多线程安全问题的,事实证明也解决不了,共享变量a还是会被随意更改。ThreadLocal无能为力。所以,一般用ThreadLocal都不会将一个共享变量放到线程的ThreadLocal中。一般来讲,存放到ThreadLocal中的变量都是当前线程本身就独一无二的一个变量。其他线程本身就不能访问,存到ThreadLocal中只是为了方便在程序中同一个线程之间传递这个变量。 因此,ThreadLocal和解决线程安全没有关系。

        例如Hibernate的代码: 

 public static final ThreadLocal<session> sessions =    
                                            new ThreadLocal<session>(); 
 public static Session currentSession() throws HibernateException {   
    Session s = sessions.get();   
    //如果从当前线程变量中获取的session为空,直接open一个session放到当前线程变量中。
    //值得注意的是,这里的session引用s是在方法体内声明的,属于局部变量,其他线程根本访问不到。
    //就是说,这里的session s本身就不是一个共享对象,本身就是当前线程独一无二的存在。
    //下一个线程来的时候,从sessions.get()拿出来的session s又是下一个线程的线程变量中的值。如果为空,同样创建。
    //所以,session在线程变量内部还是外部都是没有和任何线程共享的。
    if(s == null) {   
         s = sessionFactory.openSession();   
         sessions.set(s);   
    }   

        补充一个问题: 当使用线程池的时候,由于ThreadLocal的设计原理是将一个ThreadLocalMap的引用作为Thread的一个属性,利用当前ThreadLocal作为key,保存的变量值作为value保存在当前线程的ThreadLocalMap中的。所以ThreadLocalMap是伴随的Thread本身的存在而存在的,只要Thread不被回收,ThreadLocalMap就存在。因此,对于线程池来讲,重复利用一个Thread就等于在重复利用Thread的ThreadLocalMap,所以ThreadLocalMap里面保存的数据可能会被多次使用。 

实例:

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class ThreadLocalTest {

	public static void main(String[] args) {
		plus();
	}
	
	private static void plus() {
	    Executor executor = Executors.newFixedThreadPool(2);
	    for (int i = 0; i < 10; i++) {
	        executor.execute(new Runnable() {
	            public void run() {
	                int a = threadLocal.get();
	                threadLocal.set(++a);
	                System.out.println("plus:" + Thread.currentThread().getName() + ": " + a);

	            }
	        });
	    }
	}
	
	private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
	    protected Integer initialValue() {
	        return 0;
	    }
	};
}

        运行结果:

plus:pool-1-thread-1: 1
plus:pool-1-thread-2: 1
plus:pool-1-thread-1: 2
plus:pool-1-thread-2: 2
plus:pool-1-thread-1: 3
plus:pool-1-thread-2: 3
plus:pool-1-thread-1: 4
plus:pool-1-thread-2: 4
plus:pool-1-thread-1: 5
plus:pool-1-thread-2: 5

        可以看出,对于线程池中的两个线程来讲,虽然每次都是从ThreadLocal获取值,但是都是同一个ThreadLocalMap,所以值是递增的。
        线程重复利用,又不想重复利用其ThreadLocalMap中的值怎么办呢? 
        我的理解是: 一般情况下,我们自己用的话ThreadLocal里面都存放的是无状态的对象,只是便于在同一个线程中参数传递,所以即使重复利用也没有关系。但是如果ThreadLocal里面存放的是有状态的对象的话,在线程使用结束后直接将当前线程的ThreadLocalMap里的值设为初始值就可以了,或者直接remove掉。 
        比如 Struts2里面的ActionContext:

public class ActionContext implements Serializable {
    static ThreadLocal actionContext = new ThreadLocal();
    ....
    public static void setContext(ActionContext context) {
        actionContext.set(context);
    }
}

        在struts2的org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter类中,就会无论如何在请求结束后清除当前线程的ThreadLocalMap中的值:

} finally {
    prepare.cleanupRequest(request);
}
public void cleanupRequest(HttpServletRequest request) {
    ActionContext.setContext(null);
}

 

文章来源:http://zhangbo-peipei-163-com.iteye.com/blog/2029216

另一篇关于ThreadLocal的文章:http://bijian1013.iteye.com/blog/1871752

分享到:
评论

相关推荐

    线程安全你还在用synchronized?

    你还在用synchronized?线程安全相关知识深入剖析

    ThreadLocal:如何优雅的解决SimpleDateFormat多线程安全问题

    目录SimpleDateFormat诡异bug复现SimpleDateFormat诡异bug字符串日期转Date日期(parse)Date日期转String类型(format)SimpleDateFormat出现...注意事项使用ThreadLocal解决SimpleDateFormat线程安全问题总结...

    TestNG多线程安全吗?ThreadLocal:有我还能不安全?

    目录一、背景介绍二、TestNG多线程详解2.1 TestNG多线程实现2.2 TestNG多线程效果演示三、ThreadLocal3.1 ThreadLocal概念3.2 具体实现 ...所以就使用到ThreadLocal这个类去保证线程安全。 二、TestN

    java多线程安全性基础介绍.pptx

    java多线程安全性基础介绍 线程安全 正确性 什么是线程安全性 原子性 竞态条件 i++ 读i ++ 值写回i 可见性 JMM 由于cpu和内存加载速度的差距,在两者之间增加了多级缓存导致,内存并不能直接对cpu可见。 ...

    Java ThreadLocal 线程安全问题解决方案

    主要介绍了Java ThreadLocal 线程安全问题解决方案的相关资料,需要的朋友可以参考下

    ThreadLocal

    ThreadLocal入门教程。 讲解了线程安全和ThreadLocal的使用的基本知识。

    2、导致JVM内存泄露的ThreadLocal详解

    导致JVM内存泄露的ThreadLocal详解 为什么要有ThreadLocal ThreadLocal的使用 实现解析 引发的内存泄漏分析 错误使用ThreadLocal导致 线程不安全分析

    美团面试,问了ThreadLocal原理,这个回答让我通过了

    面试过程中他问了ThreadLocal原理(上次问线程池,这次问ThreadLocal,美团爸爸这么喜欢线程安全机制么),今天详细讲一讲ThreadLocal原理。 ThreadLocal ThreadLocal是线程的内部存储类,可以在指定线程内存储数据...

    java线程详解

    六、线程安全类 七、线程死锁 八、线程同步小结 Java线程:线程的交互 Java线程:线程的调度-休眠 Java线程:volatile关键字 Java线程:新特征-线程池 一、固定大小的线程池 二、单任务线程池 三、可变尺寸...

    java线程本地变量ThreadLocal详解

    ThreadLocal则为每一个线程提供了一个变量副本,从而隔离了多个线程访问数据的冲突,ThreadLocal提供了线程安全的对象封装,下面我们就来详细了解一下吧

    java线程安图分析(含测试代码)

    目录: 基础概念 造成线程不安全的条件 变量种类与线程安全 如何避免线程不安全 synchronized关键字使用和原理 jdk多线程并发包 THREADLOCAL 测试工程

    Java多线程编程中ThreadLocal类的用法及深入

    主要介绍了Java多线程编程中ThreadLocal类的用法及深入,尝试了自己实现一个ThreadLocal类以及对相关的线程安全问题进行讨论,需要的朋友可以参考下

    对ThreadLocal的理解【源码分析+应用举例】

    但是使用ThreadLocal对象去存储一个共享资源时,每一个线程的副本变量都指向同一个共享资源,这里也是会存在线程安全问题的。 ThreadLocal设计初衷:提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程...

    ThreadLocal、InheritableThreadLocal详解

    多线程访问同一个共享变量时,容易出现并发冲突,为了保证线程的安全,一般使用者在访问共享变量时,需要进行适量的同步。而ThreadLocal提供了线程的私有变量,每个线程都可以通过set()和get()来对这个私有变量进行...

    Java高级程序设计-多线程(二).pptx

    3.1 线程安全 多线程编程时,由于系统对线程的调度具有一定的随机性,所以,使用多个线程操作同一个数据时,容易出现线程安全问题。 当多个线程访问同一个资源时,如果控制不好,也会造成数据的不正确性。 以银行取...

    8个案例详解教会你ThreadLocal.docx

    通常情况下,我们创建的成员变量都是线程不安全的。因为他可能被多个线程同时修改,此变量对于多个线程之间彼此并不独立,是共享变量。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程无法访问和修改。也...

    jKill#basic-notes#ThreadLocal实现原理1

    通过ThreadLocal对象定位到线程:Thread.currentThread()通过ThreadLocal对象拿到所在的ThreadLocalMap: T

    Java并发编程原理与实战

    单例问题与线程安全性深入解析.mp4 理解自旋锁,死锁与重入锁.mp4 深入理解volatile原理与使用.mp4 JDK5提供的原子类的操作以及实现原理.mp4 Lock接口认识与使用.mp4 手动实现一个可重入锁.mp4 ...

Global site tag (gtag.js) - Google Analytics