Spring中bean的线程安全问题

Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。

(1)对于prototype(原型)作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。

(2)对于singleton(单例)作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。
 

有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。

无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。

对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。 

注:

prototype原型模式,每次通过Spring容器获取prototype定义的bean时,容器都将创建一个新的Bean实例,每个实例都有自己的属性和状态,而Singleton全局只有一个对象。推荐有状态的bean使用prototype作用域,而对无状态的bean使用singleton(单例)作用域。

 

也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。 

代码案例:

假设此时有一个User类,其中有一个Count变量,然后又两个线程对这一个bean进行操作。

public class UnsafeBean {
    private int count;

    public int getCount() {
        return count;
    }

    public void increment() {
        count++;
    }
}

public class UnsafeThreadDemo {
    public static void main(String[] args) {
        final UnsafeBean bean = new UnsafeBean();

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                bean.increment();
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final count: " + bean.getCount());
    }
}

 

在这个示例中,两个线程同时对 UnsafeBean 对象进行 1000 次递增操作。由于 count 属性没有同步控制,多个线程同时访问 increment() 方法可能导致竞态条件,结果可能是不确定的。

所以就可以通过ThreadLocal来使得每个线程都有自己的ThreadLocal变量副本实例,那么避免了多个线程之间竞态而导致线程安全问题;

public class ThreadLocalBean {
    private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0);

    public int getCount() {
        return count.get();
    }

    public void increment() {
        count.set(count.get() + 1);
    }
}
public class ThreadLocalBean {
    private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0);

    public int getCount() {
        return count.get();
    }

    public void increment() {
        count.set(count.get() + 1);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值