栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring的线程安全问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring的线程安全问题

概述

我们知道Spring IoC 容器中只会存在一个 bean 的实例,无论一次调用还是多次调用,始终指向的都是同一个 bean 对象。

对于单实例来说,所有线程都共享同一个 bean 实例,自然就会发生资源的争抢,从而导致线程不安全。

举例:
新增服务类ThreadUnSafeService

@Service
public class ThreadUnSafeService {
    public int i;

    public void add() {
        i++;
    }

    public void sub() {
        i--;
    }

    public int getValue() {
        return i;
    }
}

启动十个线程,每个线程各做一千次加减:

public class TestSpring {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                new String[] { "applicationContext.xml" });

        for (int j = 0; j < 10; j++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    ThreadUnSafeService service = (ThreadUnSafeService) context.getBean("threadUnSafeService");
                    for (int i = 0; i < 1000; i++) {
                        service.add();
                    }
                    for (int i = 0; i < 1000; i++) {
                        service.sub();
                    }
                    System.out.println(Thread.currentThread().getName() + "-" + service.getValue());
                }
            }).start();
        }
    }
}

输出结果:

Thread-2-0
Thread-3-0
Thread-8-1201
Thread-10-663
Thread-1-1443
Thread-9-1380
Thread-7-1846
Thread-6-488
Thread-4-2507
Thread-5-2856

从结果可以看出,运行结果都不是 0,这明显的是线程不安全!!

因为 10 个线程获取的 ThreadUnSafe 实例都是同一个,并且 10 个线程都对同一个资源 i 发生了争抢,所以才会导致线程安全问题的发生。

解决方案一:scope 的值改为 prototype
@Service
@Scope("prototype")
public class ThreadSafeService {
    public int i;

    public void add() {
        i++;
    }

    public void sub() {
        i--;
    }

    public int getValue() {
        return i;
    }
}

输出结果:

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

prototype 作用域下,每次获取的 ThreadUnSafe 实例都不同,所以自然不会有线程安全的问题。

解决方案二:将bean改为无状态的

无状态bean就是没有共享变量,所以就不会产生线程安全问题。

@Service
public class ThreadSafeService {
    public void getValue() {
        int val = 0;
        for (int i = 0; i < 1000; i++) {
            val++;
        }
        for (int i = 0; i < 1000; i++) {
            val--;
        }
        System.out.println(val);
    }
}

调用:

public class TestSpring {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                new String[] { "applicationContext.xml" });

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    ThreadSafeService service = (ThreadSafeService) context.getBean("threadSafeService");
                    service.getValue();
                }
            }).start();
        }
    }
}

输出结果:

0
0
0
0
0
0
0
0
0
0
解决方案三:加锁

既然是线程安全问题,那就加锁。

public class TestSpring {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                new String[]{"applicationContext.xml"});

        for (int j = 0; j < 10; j++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    ThreadUnSafeService service = (ThreadUnSafeService) context.getBean("threadUnSafeService");

                    synchronized (service) {
                        for (int i = 0; i < 1000; i++) {
                            service.add();
                        }
                        for (int i = 0; i < 1000; i++) {
                            service.sub();
                        }
                        System.out.println(Thread.currentThread().getName() + "-" + service.getValue());
                    }
                }
            }).start();
        }
    }
}

运行结果为 0。

毫无疑问加锁确实可以,但是加锁会增加性能上的开销。

解决方案四:ThreadLocal

ThreadLocal 在自己线程内创建一个变量的副本,规避了线程安全问题。

@Service
public class ThreadUnSafeService {

    public static ThreadLocal i = new ThreadLocal<>();

    public void add() {
        Integer integer = i.get();
        if (null == integer){
            integer = 0;
        }
        integer++;
        i.set(integer);
    }

    public void sub() {
        Integer integer = i.get();
        if (null == integer){
            integer = 0;
        }
        integer--;
        i.set(integer);
    }

    public int getValue() {
        return i.get();
    }
}

运行结果为 0。

但是ThreadLocal为每个线程创建变量的副本,带来了空间上的开销。

总之,解决线程安全问题思路分为两种。第一种,消除共享变量,是线程不安全问题不存在,方法有将bean改为无状态的、scope 的值改为 prototype、ThreadLocal。第二种,就是加锁。

Spring自身方案

Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的“状态性对象”采用ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。

一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9-2所示。

这样用户就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有对象所访问的同一ThreadLocal变量都是当前线程所绑定的。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/970277.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号