精华内容
下载资源
问答
  • 先解释一下,在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们...

    ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。

      这个玩意有什么用处,或者说为什么要有这么一个东东?先解释一下,在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal就比较好的解决了这个问题。

      我们从源码的角度来分析这个问题。

      首先定义一个ThreadLocal:

    public class ConnectionUtil {
        private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
        private static Connection initConn = null;
        static {
            try {
                initConn = DriverManager.getConnection("url, name and password");
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        
        public Connection getConn() {
            Connection c = tl.get();
            if(null == c) tl.set(initConn);
            return tl.get();
        }
        
    }

      这样子,都是用同一个连接,但是每个连接都是新的,是同一个连接的副本。

      那么实现机制是如何的呢?

      1、每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

      2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

      3、当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。

      4、总结:当我们调用get方法的时候,其实每个当前线程中都有一个ThreadLocal。每次获取或者设置都是对该ThreadLocal进行的操作,是与其他线程分开的。

      5、应用场景:当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。

      6、其实说再多也不如看一下源码来得清晰。如果要看源码,其中涉及到一个WeakReference和一个Map,这两个地方需要了解下,这两个东西分别是a.Java的弱引用,也就是GC的时候会销毁该引用所包裹(引用)的对象,这个threadLocal作为key可能被销毁,但是只要我们定义成他的类不卸载,tl这个强引用就始终引用着这个ThreadLocal的,永远不会被gc掉。b.和HashMap差不多。

      事实上,从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。如果允许的话,我们自己就能实现一个这样的功能,只不过恰好JDK就已经帮我们做了这个事情。

    ThreadLocal应用范例:

    http://www.cnblogs.com/wangzhongqiu/p/7542110.html

     

    展开全文
  • Java多线程编程中,存在很多线程安全问题,至于什么线程安全呢,给出一个通俗易懂的概念还是蛮...静态成员变量线程非安全(无论单例或者非单例皆不安全)。 静态变量即类变量,位于方法区,所有对象共享,共...

    Java多线程编程中,存在很多线程安全问题,至于什么是线程安全呢,给出一个通俗易懂的概念还是蛮难的,如同《java并发编程实践》中所说:

       写道

    给线程安全下定义比较困难。存在很多种定义,如:“一个类在可以被多个线程安全调用时就是线程安全的”。 

     此处不赘述了,首先给出静态变量、实例变量、局部变量在多线程环境下的线程安全问题结论,然后用示例验证,请大家擦亮眼睛,有错必究,否则误人子弟!

    静态成员变量:线程非安全(无论单例或者非单例皆不安全)。

    静态变量即类变量,位于方法区,为所有对象共享,共享一份内存,一旦静态变量被修改,其他对象均对修改可见,故线程非安全。

    实例成员变量:单例模式(只有一个对象实例singleton存在)线程非安全,非单例线程安全。

    实例变量为对象实例私有,在虚拟机的堆heap中分配,若在系统中只存在一个此对象的实例,在多线程环境下,“犹如”静态变量那样,被某个线程修改后,其他线程对修改均可见,故线程非安全(如,springmvc controller是单例的,非线程安全的);如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变量的修改将互不影响,故线程安全(如,struts2 action默认是非单例的,每次请求在heap中new新的action实例,故struts2 action可以用实例成员变量)。

    局部变量:线程安全(线程封闭性Thread Confinement)。

    每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。

    〖by self:上述所有的变量均指,共享的(Shared)和可变的(Mutable,进行了'read''write'操作)状态变量,只有对这样的变量讨论线程安全才有意义,所有实际上上述的“局部变量”一定是线程安全的,因为其不是共享的(Not Shared),至于非状态变量,毫无疑问,是线程安全的。〗

    静态成员变量 线程安全问题模拟:

    /**   
      * 线程安全问题模拟执行   
      *  ------------------------------   
      *       线程1      |    线程2   
      *  ------------------------------   
      *   static_i = 4;  | 等待   
      *   static_i = 10; | 等待   
      *    等待          | static_i = 4;   
      *   static_i * 2;  | 等待   
      *  -----------------------------  
     * */    
    public class Test implements Runnable    
    {    
        private static int static_i;//静态变量     
            
        public void run()    
        {    
            static_i = 4;    
            System.out.println("[" + Thread.currentThread().getName()    
                    + "]获取static_i 的值:" + static_i);    
            static_i = 10;    
            System.out.println("[" + Thread.currentThread().getName()    
                    + "]获取static_i*2的值:" + static_i * 2);    
        }    
            
        public static void main(String[] args)    
        {    
            Test t = new Test();    
            //启动尽量多的线程才能很容易的模拟问题     
            for (int i = 0; i < 3000; i++)    
            {    
                //t可以换成new Test(),保证每个线程都在不同的对象中执行,结果一样     
                new Thread(t, "线程" + i).start();    
            }    
        }    
    }

    根据代码注释中模拟的情况,当线程1执行了static_i = 4;  static_i = 10; 后,线程2获得执行权,static_i = 4; 然后当线程1获得执行权执行static_i * 2;  必然输出结果4*2=8,按照这个模拟,我们可能会在控制台看到输出为8的结果。

    写道

    [线程27]获取static_i 的值:4 
    [线程22]获取static_i*2的值:20 
    [线程28]获取static_i 的值:4 
    [线程23]获取static_i*2的值:8 
    [线程29]获取static_i 的值:4 
    [线程30]获取static_i 的值:4 
    [线程31]获取static_i 的值:4 
    [线程24]获取static_i*2的值:20

    实例成员变量 线程安全问题模拟:

    public class Test implements Runnable    
    {    
        private int instance_i;//实例变量    
            
        public void run()    
        {    
            instance_i = 4;    
            System.out.println("[" + Thread.currentThread().getName()    
                    + "]获取instance_i 的值:" + instance_i);    
            instance_i = 10;    
            System.out.println("[" + Thread.currentThread().getName()    
                    + "]获取instance_i*2的值:" + instance_i * 2);    
        }    
            
        public static void main(String[] args)    
        {    
            Test t = new Test();    
            //启动尽量多的线程才能很容易的模拟问题     
            for (int i = 0; i < 3000; i++)    
            {    
                //每个线程对在对象t中运行,模拟单例情况    
                new Thread(t, "线程" + i).start();    
            }    
        }    
    } 

    按照本文开头的分析,犹如静态变量那样,每个线程都在修改同一个对象的实例变量,肯定会出现线程安全问题。

    写道

    [线程66]获取instance_i 的值:10 
    [线程33]获取instance_i*2的值:20 
    [线程67]获取instance_i 的值:4 
    [线程34]获取instance_i*2的值:8 
    [线程35]获取instance_i*2的值:20 
    [线程68]获取instance_i 的值:4

    从结果可知单例情况下,实例变量线程非安全。 

    将new Thread(t, "线程" + i).start();改成new Thread(new Test(), "线程" + i).start();模拟非单例情况,会发现不存在线程安全问题。

     

     局部变量线程安全问题模拟:

    public class Test implements Runnable    
    {    
        public void run()    
        {    
            int local_i = 4;    
            System.out.println("[" + Thread.currentThread().getName()    
                    + "]获取local_i 的值:" + local_i);    
            local_i = 10;    
            System.out.println("[" + Thread.currentThread().getName()    
                    + "]获取local_i*2的值:" + local_i * 2);    
        }    
            
        public static void main(String[] args)    
        {    
            Test t = new Test();    
            //启动尽量多的线程才能很容易的模拟问题    
            for (int i = 0; i < 3000; i++)    
            {    
                //每个线程对在对象t中运行,模拟单例情况     
                new Thread(t, "线程" + i).start();    
            }    
        }    
    } 

    控制台没有出现异常数据。

     

    静态方法 是否是线程安全的:

    public class  Test{  
    public static  String hello(String str){  
        String tmp="";  
        tmp  =  tmp+str;  
       return tmp;  
    }  
    }  

    hello方法会不会有多线程安全问题呢?不会!

    静态方法如果没有使用静态变量,则没有线程安全问题。

    为什么呢?因为静态方法内声明的变量,每个线程调用时,都会新创建一份,而不会共用一个存储单元。比如这里的tmp,每个线程都会创建自己的一份,因此不会有线程安全问题。

    注意,静态成员变量,由于是在类加载时占用一个存储区,每个线程都是共用这个存储区的,所以如果在静态方法里使用了静态成员变量,这就会有线程安全问题。即只要方法内含有静态成员变量,就是非线程安全的,(实际上归根到底还是变量的线程安全问题~)。

    展开全文
  • 变量:static关键字修饰的变量为什么叫类变量,意思就是他是属于这个类的,可以用类名.变量名这样用,而不是这个类的对象的变量,当让这个类的对象也是可以调用的,类变量是用类名.变量名直接用的,而无须实例化...

    类变量:static关键字修饰的变量,为什么叫类变量,意思就是他是属于这个类的,可以用类名.变量名这样用,而不是这个类的对象的变量,当让这个类的对象也是可以调用的,类变量是用类名.变量名直接用的,而无须实例化这个类的对象来调用

    成员变量和局部变量java中 变量分成员变量 和局部变量 成员变量是指这个类的变量,局部变量是类中方法体内定义的变量

    记住一个原则即可:方法体中的引用变量和基本类型的变量都在栈上,其他都在堆上

    所以B对象里面所有东西都在堆上,main方法中的b变量在栈上。

    对于局部变量,如果是基本类型,会把值直接存储在栈;如果是引用类型,比如String s = new String("william");会把其对象存储在堆,而把这个对象的引用(指针)存储在栈

    成员变量作为对象的属性,当然是放在堆里了。对象在堆里,对象中的内容就是各种字段。
    只有方法执行的时候所用到的各种指令参数才会入栈出栈。
    类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中),基本类型和引用类型的成员变量都在这个对象的空间中,作为一个整体存储在堆。而类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。(对象实质上就是各种成员变量,不包括方法)

    局部变量存在于jvm的什么区域内:

    基本类型是存在栈中
    非通过new创建的字符串对象是存在jvm的字符池中
    其余的都存放在堆中

    展开全文
  • 为什么StringBuilder是线程不安全

    千次阅读 2019-08-30 14:40:12
    通常我们都知道说StringBuilder是线程不安全的,那如果继续追问下去,为什么StringBuilder是线程不安全的,该怎么回答呢? 首先需要明确地知道StringBuilder它内部的组织结构 来看源代码中,StringBuilder的抽象...

    通常我们都知道说StringBuilder是线程不安全的,那如果继续追问下去,为什么StringBuilder是线程不安全的,该怎么回答呢?

    首先需要明确地知道StringBuilder它内部的组织结构

    1. 来看源代码中,StringBuilder的抽象父类AbstractStringBuilder的两个重要的成员变量

      /**
       * The value is used for character storage.
       * char数组存储使用的字符
       */
      char[] value;
      
      /**
       * The count is the number of characters used.
       * 使用的字符的数量
       */
      int count;
      

      append方法

      public AbstractStringBuilder append(String str) {
              if (str == null)
                  return appendNull();
              int len = str.length();
              ensureCapacityInternal(count + len);
              str.getChars(0, len, value, count);
              count += len;
              return this;
          }
      

      代码第五行是检查需要拼接的字符串长度跟已使用的字符长度之和是否超过char数组的长度,来决定是否扩容
      可以看一下具体的代码实现逻辑

      private void ensureCapacityInternal(int minimumCapacity) {
              // overflow-conscious code
              if (minimumCapacity - value.length > 0) {
                  value = Arrays.copyOf(value,
                          newCapacity(minimumCapacity));
              }
          }
      

      还可以继续看一下扩容规则,newCapacity方法

      private int newCapacity(int minCapacity) {
              // overflow-conscious code
              int newCapacity = (value.length << 1) + 2;
              if (newCapacity - minCapacity < 0) {
                  newCapacity = minCapacity;
              }
              return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
                  ? hugeCapacity(minCapacity)
                  : newCapacity;
          }
      

      第三行的意思就是扩容为原数组长度的2倍再加2,如果扩容之后的长度还是小于待拼接之后的长度,那直接使用待拼接之后的长度,后面的逻辑就是说,如果达到了最大容量的情况,这里不做讨论了,可以继续往下看源码,也是不难理解的

      接着回到append方法的第六行,str.getChars方法实现了将指定字符串拼接到char数组中
      可以看一下源码

      public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
              if (srcBegin < 0) {
                  throw new StringIndexOutOfBoundsException(srcBegin);
              }
              if (srcEnd > value.length) {
                  throw new StringIndexOutOfBoundsException(srcEnd);
              }
              if (srcBegin > srcEnd) {
                  throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
              }
              System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
          }
      

      前面是一系列参数校验,最后是调用一个native本地方法,System.arraycopy实现两个char数组从指定位置copy
      这里为什么是两个char数组中?
      因为String内部也是通过一个char数组维护字符串,只不过这个char数组成员变量是final修饰的

      继续回到append方法,第七行就是将成员变量count(使用的字符数量)加上拼接上的字符串长度

      至此完成append方法

    2. 前面详细分析了StringBuilder的append方法,那为什么他就是线程不安全的呢?

      提供一个场景:

      现在char数组value的长度为5,已使用的字符数count为4
      在这里插入图片描述
      线程1和线程2都运行到了append方法的第五行结束,都没有触发扩容

      现在线程2抢占到cpu时间片,运行第六行,将g拼接上,count=5,char数组已满,如图
      在这里插入图片描述
      那这个时候线程1再来接着运行,append方法第六行,前面分析过了str.getChars方法最终会调用一个native本地方法,System.arraycopy,可以看一下源码
      在这里插入图片描述
      线程1肯定会报ArrayIndexOutofBoundsException,这就是多线程情况下StringBuilder不安全问题

      看到这里应该就明白了吧,在多线程情况下,StringBuilder会出现拼接的时候发生异常导致的不安全问题

      在这里插入图片描述

      这个异常其实不易出现,我运行了快十次才出现一次,就赶紧截图啦。

    3. 还有一点线程不安全的情况:
      在append方法的第7行,如果两个线程同时运行到这里之前,拿到的count相同的,然后继续往下执行,这样的话到最后count的值必定会少算,这个通过测试结果也很好出现。

      public static void main(String[] args) throws InterruptedException {
              for (int i = 0; i < 100; i++) {
                  new Thread(() -> {
                      for (int j = 0; j < 100; j++) {
                          str.append("a");
                      }
                  }).start();
              }
              Thread.sleep(2000);
              System.out.println(str.length());
          }
      

      在这里插入图片描述

    4. 跟他相对的StringBuffer类就是一个线程安全的字符串类,可以看一下他的源代码append方法

      	@Override
          public synchronized StringBuffer append(String str) {
              toStringCache = null;
              super.append(str);
              return this;
          }
      

      都加了synchronized锁,所以肯定是线程安全的,但是效率肯定就是低啦。。

    5. 总结:
      详细讲解了StringBuilder的append方法实现逻辑以及多线程情况下会出现的问题
      以上都是自己个人总结,源码分析以及图片说明,如有问题,谢谢指正

    展开全文
  • 先解释一下,在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们...
  • 说的很清楚,SimpleDateFormat 不是线程安全的,多线程下需要每个线程创建不同的实例。 不安全的原因是因为使用了 Calendar 这个全局变量: 在日期格式化的时候: 我们把重点放在 calendar ,这个 ...
  • 因为原生的servlet在整个application生命周期中...既然是共享,那么任何一个人修改都会造成其他人数据的改变,所以servlet年代,成员变量能作为参数存放的,只能在具体每个线程访问的方法中单独获取方法定义变量...
  • 首先我们应该知道线程安全问题一般发生在成员变量上,这是为什么啦?因为成员变量是存放在堆内存中,而堆内存又是线程共享的,这就造成了线程安全问题因为Spring中的Bean默认是单例的,所以在定义成员变量时也有可能...
  • 线程安全的概念 1、多线程读写同一个...变量值发生变化,线程不安全的; 变量值始终不能发生变化,线程安全的; 方法变量---线程安全的,线程私有局部变量; 2、 多例 线程安全的;(i++是属于线程私有变量) spr...
  • 2、成员变量会受到多线程影响。 多个线程调用的同一个对象的同一个方法: 如果方法里无成员变量受任何影响, 如果方法里有成员变量,只有读操作,受影响,存在赋值操作,有影响。第一点 SessionFactory和...
  • 以前没有注意到SimpleDateFormat线程不安全的问题,写时间工具类,一般写成静态的成员变量,不知,此种写法的危险性!在此讨论一下SimpleDateFormat线程不安全问题,以及解决方法。 为什么SimpleDateFormat不安全?...
  • Spring解决单例bean线程不安全问题

    千次阅读 多人点赞 2019-05-27 17:32:11
    首先我们应该知道线程安全问题一般发生在成员变量上,这是为什么啦? 因为成员变量是存放在堆内存中,而堆内存又是线程共享的,这就造成了线程安全问题 因为Spring中的Bean默认是单例的,所以在定义成员变量时也...
  • SimpleDateFormat线程不安全及解决办法

    万次阅读 2019-04-09 11:26:25
    以前没有注意到SimpleDateFormat线程不安全的问题,写时间工具类,一般写成静态的成员变量,不知,此种写法的危险性!在此讨论一下SimpleDateFormat线程不安全问题,以及解决方法。 为什么SimpleDateFormat不安全? ...
  • 线程安全

    2018-06-14 16:08:00
    2.产生线程不安全的原因 多个线程访问同一相同资源,并且有线程执行了写操作,可能会出现线程安全问题。 2.怎样做到类线程安全 无状态 :没有成员变量的类,也就不存在共享同一资源了。 让类不可变:所有成员变量定义...
  • 先解释一下,在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都 在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们...
  • bean(包含Controller, Service, DAO, PO, VO)在使用过程中,如果使用方式无状态的(无状态即bean中只有方法,无成员变量,只有方法里面的局部变量,局部变量都在栈中,而栈是线程私有的),那么就是安全的。...
  • Java并发(1) i++的线程安全问题

    千次阅读 2020-05-24 17:52:01
    此处可能是局部变量,肯定是成员变量或静态变量 PS:为什么不可能是局部变量? 需要多线程访问局部变量,那么多个线程就必须在方法中进行定义,如使用匿名内部类方式定义多个线程。 在使用匿名内部类引用局部...
  • 今天在代码中看到在方法中使用SimpleDateFormat这儿类,我就想既然使用的格式化的格式是一样的为什么要在每次都new一个对象出来呢,而不是使用在成员变量中?? 带着这个问题,查询的一些关于SimpleDateFormat的format这...
  • 首先要明确static是干嘛的,static成员变量在类初次被加载的时候初始化,而且在内存中只有一份,所有实例共享,也就是大家都是操作的同一个变量,但是他是能保证线程安全的。这个可以写个小demo,很好证明。 2。...
  • 初学Java Web 开发的者,常会忽略Servlet的特性:非线程安全。 所谓线程安全就是:每一次调用是独立的结果,...再来说为什么Servlet是非线程安全:因为Servlet的所谓生命周期是由Web服务器的Servlet容器管理的,Web...
  • 面试官问了我这个问题,如何保证Spring并发访问的线程安全问题,当时的我也只是对这些有所了解,仅仅知道spring中的controller、serivce、dao默认单例,只要在这些单例中使用成员变量就可以了,然后工作至今,我...
  • Lambda表达式所使用的外部变量能是可变的局部变量 (也就是说它只能接受成员变量,final的局部变量和参数)下面例子中的test就是一个外部变量实际上就是说,它能接受非final的保存在线程祯中的变量, 等等, 这句话看...
  • 因为用户调用的都是类里面的方法,方法里面的都是局部变量,没有对象单例一说法,且方法里面的变量都是new 新一个对象了,所以不会线程不安全。 对象实例化? 就是写好一个类定义人类,类有跑的动作,有姓名,

空空如也

空空如也

1 2 3 4 5 ... 11
收藏数 215
精华内容 86
关键字:

为什么成员变量线程不安全