一、概述
我们知道Spring中的bean,默认情况下是单例的,那么Spring中的bean是线程安全的吗?这个需要分情况考虑,bean中是否存在成员变量?bean中的成员变量是怎么处理的?...,针对bean的状态会有不同的处理方案:
情况一:bean是单例的;
情况二:bean是多例的(不会存在线程安全问题);
出现线程安全问题的原因:单实例bean中存在成员变量,并且有对这个bean进行读写的操作,因此出现了线程安全的问题。
二、演示Spring bean存在线程安全
2.1、UserService
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/26 14:55* @Description: */
@Service
public class UserService {private String username;public String welcome(String name) {username = "welcome " + name;try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}return username;}}
2.2、MySpringConfig
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/23 15:29* @Description:*/
@Configuration
@ComponentScan(basePackages = {"org.star"})
public class MySpringConfig {}
2.3、AopFullAnnotationMainApp
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/23 15:14* @Description:*/
@Slf4j
public class AopFullAnnotationMainApp {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);UserService userService = context.getBean(UserService.class);Random r = new Random();String[] nameArray = new String[]{"张三", "李四", "王五", "赵六", "钱七"};for (int i = 1; i <= 5; i++) {new Thread(() -> {int index = r.nextInt(5);String name = nameArray[index];log.info("当前线程:{},当前索引:{},当前name的值:{},当前取出的值:{}", Thread.currentThread().getName(), index, name, userService.welcome(name));}, "线程" + i).start();}}}
三、解决方法
上面代码演示了Spring中的bean的确存在着线程安全问题,出现问题我们要解决问题,针对Spring中bean中存在的线程安全,我们可以通过以下方式进行解决:
方案一:将成员变量修改为局部变量(单例bean);
方案二:使用ThreadLocal(单例bean);
方案三:使用同步锁synchronized(单例bean);
方案四:将单例bean设置为多例的;
案例代码如下:
3.1、将成员变量修改为局部变量(单例bean)
3.1.1、UserService2
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/26 14:55* @Description: 单例bean线程不安全(解决方式一:将成员变量修改为局部变量)*/
@Service
public class UserService2 {public String welcome(String name) {String username = "welcome " + name;try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}return username;}}
3.1.2、MySpringConfig(同上)
3.1.3、AopFullAnnotationMainApp
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/23 15:14* @Description:*/
@Slf4j
public class AopFullAnnotationMainApp {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);UserService2 userService2 = context.getBean(UserService2.class);Random r = new Random();String[] nameArray = new String[]{"张三", "李四", "王五", "赵六", "钱七"};for (int i = 1; i <= 5; i++) {new Thread(() -> {int index = r.nextInt(5);String name = nameArray[index];log.info("当前线程:{},当前索引:{},当前name的值:{},当前取出的值:{}", Thread.currentThread().getName(), index, name, userService2.welcome(name));}, "线程" + i).start();}}
}
3.2、使用ThreadLocal(单例bean)
3.2.1、UserService3
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/26 14:55* @Description: 单例bean线程不安全(解决方式二:使用ThreadLocal)*/
@Service
public class UserService3 {private ThreadLocal<String> threadLocal = new ThreadLocal<>();public String welcome(String name) {threadLocal.set("welcome" + name);try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}return threadLocal.get();}}
3.2.2、MySpringConfig(同上)
3.2.4、AopFullAnnotationMainApp
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/23 15:14* @Description:*/
@Slf4j
public class AopFullAnnotationMainApp {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);UserService3 userService3 = context.getBean(UserService3.class);Random r = new Random();String[] nameArray = new String[]{"张三", "李四", "王五", "赵六", "钱七"};for (int i = 1; i <= 5; i++) {new Thread(() -> {int index = r.nextInt(5);String name = nameArray[index];log.info("当前线程:{},当前索引:{},当前name的值:{},当前取出的值:{}", Thread.currentThread().getName(), index, name, userService3.welcome(name));}, "线程" + i).start();}}
}
3.3、使用同步锁synchronized(单例bean)
3.3.1、UserService4
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/26 14:55* @Description: 单例bean线程不安全(解决方式三:使用同步锁synchronized)*/
@Service
public class UserService4 {private String username;public synchronized String welcome(String name) {username = "welcome " + name;try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}return username;}}
3.3.2、MySpringConfig(同上)
3.3.3、AopFullAnnotationMainApp
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/23 15:14* @Description:*/
@Slf4j
public class AopFullAnnotationMainApp {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);UserService4 userService4 = context.getBean(UserService4.class);Random r = new Random();String[] nameArray = new String[]{"张三", "李四", "王五", "赵六", "钱七"};for (int i = 1; i <= 5; i++) {new Thread(() -> {int index = r.nextInt(5);String name = nameArray[index];log.info("当前线程:{},当前索引:{},当前name的值:{},当前取出的值:{}", Thread.currentThread().getName(), index, name, userService4.welcome(name));}, "线程" + i).start();}}
}
3.4、将单例bean设置为多例的
3.4.1、UserService5
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/26 14:55* @Description: 单例bean线程不安全(解决方式四:将单例bean设置为多例的)*/
@Scope("prototype")
@Service
public class UserService5 {private String username;public String welcome(String name) {username = "welcome " + name;try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}return username;}}
3.4.2、MySpringConfig(同上)
3.4.3、AopFullAnnotationMainApp
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/23 15:14* @Description:*/
@Slf4j
public class AopFullAnnotationMainApp {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);Random r = new Random();String[] nameArray = new String[]{"张三", "李四", "王五", "赵六", "钱七"};for (int i = 1; i <= 5; i++) {new Thread(() -> {UserService5 userService5 = context.getBean(UserService5.class);int index = r.nextInt(5);String name = nameArray[index];log.info("当前线程:{},当前索引:{},当前name的值:{},当前取出的值:{}", Thread.currentThread().getName(), index, name, userService5.welcome(name));}, "线程" + i).start();}}
}