zoukankan      html  css  js  c++  java
  • 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/

  • 相关阅读:
    Spring AOP获取拦截方法的参数名称跟参数值
    mybatis generator逆向工程自动生成带中文注释修改版(添加了实体类注释)文末附有git下载地址
    关于Java编写多行注释遇到方法字符串中正好也有注释符号产生冲突的解决办法
    SpringBoot入门学习以及整合MyBatis
    IO跟NIO的区别
    redis的配置文件详解redis.conf
    Redis入门基础内容(转载整理非原创)
    深入网络协议来理解数据传输三(http协议详解)
    深入网络协议来理解数据传输二(转载整理)
    Python编写ATM(初级进阶)
  • 原文地址:https://www.cnblogs.com/stevenlii/p/8663816.html
Copyright © 2011-2022 走看看