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/

posted @ 2018-03-28 14:53  stevenlii  阅读(405)  评论(0编辑  收藏  举报