• Spring线程安全为何非安全,场景重现,解决安全小结


    1、Spring线程安全吗?

    不安全

    2、为什么

    Spring对bean的作用域默认是单例的,bean(包含Controller, Service, DAO, PO, VO)在使用过程中,如果使用方式为无状态的(无状态即bean中只有方法,无成员变量,只有方法里面的局部变量,局部变量都在栈中,而栈是线程私有的),那么就是安全的。

    但是当bean成为了有状态的,如在service的成员变量中定义了vo,那么此vo则为不安全的。

    为什么成员变量不安全:因为成员变量没有在栈上,栈上存的是局部变量表。成员变量作为类的信息,存在堆内存上,堆是线程共享的。线程私有的只有栈和程序计数器。

    3、不安全的场景重现一下?

    我假设MyThread就是一个service,ticket作为成员变量(当然也可以是一个vo bean,如User/Teacher之类)

    1 class MyThread extends Thread {
    2     private int ticket = 10;
    3 
    4     public void run() {
    5         while (ticket > 0) {
    6                 System.out.print(Thread.currentThread().getName()+"thread:" + ticket--+",");
    7             }
    8     }
    9 }

    这时候假设是spring的默认作用域为单例(所以MyThread为单例),来了三个请求(即三个线程)

    如下Test,则因为成员变量ticket没有被锁保护,则其变量的增加或减少的操作是会窜数据的。

    public class Test {
    
        public static void main(String[] args) throws Exception {
            MyThread mt = new MyThread();
            new Thread(mt).start();
            new Thread(mt).start();
            new Thread(mt).start();
        }
    }
    
    
    

     4、解决办法

    a.需要的时候创建新实例:改变Spring的作用域为Prototype,这样每个请求就会创建一个Service等bean,保证了安全,如下

    public class Test {
    
        public static void main(String[] args) throws Exception {

    MyThread tr1 = new MyThread();
    Thread td1 = new Thread(tr1,"aa");
    MyThread tr2 = new MyThread();
    Thread td2 = new Thread(tr2,"bb");

            td1.start();
            td2.start();
        }
    }

    每次都是一个新的MyThread,这样每次成员变量都是一个类的,不存在窜数据,用空间换时间。

    不过针对变量,就都是每个bean自己的了,需要每个线程自己处理自己的,结果:

    bbthread:10,aathread:10,bbthread:9,bbthread:8,bbthread:7,bbthread:6,aathread:9,bbthread:5,aathread:8,bbthread:4,bbthread:3,bbthread:2,bbthread:1,aathread:7,aathread:6,aathread:5,aathread:4,aathread:3,aathread:2,aathread:1, 

    b. 使用同步:同步成员变量(如ticket)【加了synchronized 关键字】

    package com.bjsxt.test;
    public class Test {
    
        public static void main(String[] args) throws Exception {
            MyThread mt = new MyThread();
            new Thread(mt).start();
            new Thread(mt).start();
            new Thread(mt).start();
    
    
        }
    }
    
    class MyThread extends Thread {
        private int ticket = 10;
    
        public synchronized void run() {
            
            while (ticket > 0) {
                    System.out.print(Thread.currentThread().getName()+"thread:" + ticket--+",");
                }
        }
    }

    用时间换了空间,也保证了安全。适用于共享成员变量的时候

    结果:

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

    c、使用ThreadLocal:

    原理:

    Theradlocal是空间换时间的又一种方法,他用浅拷贝(深浅copy: 浅c就是指针指向同一个对象,深c,则是重新开一块内存。),保证了每个线程有自己的变量。

    示例:

    package com.bjsxt.test;
    public class Test {
    
        public static void main(String[] args) throws Exception {
            MyThread mt = new MyThread();
            new Thread(mt,"aa").start();
            new Thread(mt,"bb").start();
            new Thread(mt,"cc").start();
    
    
        }
    }
    
    class MyThread extends Thread {
        private int ticket_Default = 10;
        private  ThreadLocal<Integer> threadLocalticket = new ThreadLocal<Integer>(); 
        public  Integer getThreadLocalticket()   
        {  
            Integer curticket = threadLocalticket.get();  
            if(curticket==null){  
                threadLocalticket.set(ticket_Default);  
            }  
            return threadLocalticket.get();
        }  
        public void run() {
            int ticket = getThreadLocalticket();
            while (ticket > 0) {
                    System.out.print(Thread.currentThread().getName()+"thread:" + ticket--+",");
                }
        }
    }

    运行结果:

    bbthread:10,aathread:10,ccthread:10,aathread:9,bbthread:9,aathread:8,ccthread:9,aathread:7,bbthread:8,aathread:6,ccthread:8,aathread:5,bbthread:7,aathread:4,ccthread:7,aathread:3,bbthread:6,aathread:2,aathread:1,ccthread:6,ccthread:5,ccthread:4,ccthread:3,ccthread:2,ccthread:1,bbthread:5,bbthread:4,bbthread:3,bbthread:2,bbthread:1,

    和3.a一样的结果

    4,题外话

    4.1 Struts2是基于4.a的方法做的,会把每个controller都创建一个,创建又需要回收,非常浪费内存和性能。另外spring是根据方法进行aop拦截的,所以创建的很多,回收的也需要很多。。

    4.2 因为Threadlocal是浅拷贝,如果变量是一个引用类型,那么就要考虑它内部的状态是否会被改变,想要解决这个问题可以通过重写ThreadLocal的initialValue()函数来自己实现深拷贝,建议在使用ThreadLocal时一开始就重写该函数

    更多Theadlocal见参考文章一

    4.3 Servlet是线程安全的吗?见参考文章五

    5、参考

    <一>聊一聊 Spring 中的线程安全性+Theadlocal
    http://www.importnew.com/27440.html

    <二>Spring框架中的单例Beans是线程安全的么
    https://blog.csdn.net/u011202334/article/details/51585648

    <三>Spring单例与线程安全小结 
    https://www.cnblogs.com/doit8791/p/4093808.html

    <四>深浅拷贝解析
    https://blog.csdn.net/u011420067/article/details/52460183

     <五>深入理解Servlet线程安全问题 

    https://blog.csdn.net/lcore/article/details/8974590

    https://blog.csdn.net/qq_24145735/article/details/52433096

    <六>

    Servlet 工作原理解析
    https://www.ibm.com/developerworks/cn/java/j-lo-servlet/

  • 相关阅读:
    正则表达式的妙用获得数组
    道不远人,话IO框架
    页面和控件的生命周期及其二者的关系
    深度解析 TypeConverter & TypeConverterAttribute (二)
    今天我顺利的在cnblogs安家,我要在这里写下辉煌
    AJAX or Ajax
    深度解析 TypeConverter & TypeConverterAttribute (一)
    SonarQube简单入门
    vuecli · Failed to download repo vuejstemplates/webpack: connect ETIMEDOUT 192.30.253.112:443
    Sqlmap使用教程
  • 原文地址:https://www.cnblogs.com/stevenlii/p/8663816.html
Copyright © 2020-2023  润新知