一、概述
我们知道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();
}
}
}