变量的线程安全分析-实例分析
实例分析
例1
Servlet 是运行在 tomcat 下面的,只有一个实例,会被 tomcat 的多个线程所共享使用,所以里面的成员变量都有可能存在线程安全问题。
public class MyServlet extends HttpServlet {
// 是否安全? 不安全
Map<String,Object> map = new HashMap<>();
// 是否安全? 安全:字符串不可变,是线程安全的
String S1 = "...";
// 是否安全? 安全
final String S2 = "...";
// 是否安全? 不安全:会被共享
Date D1 = new Date();
// 是否安全? 不安全:D2成员变量的引用值固定了,不能变。但是日期里面的其他属性是可以变的
final Date D2 = new Date();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
// 使用上述变量
}
}
例2
UserService 是 MyServlet的成员变量,但是 UserService 里面又存在 count 成员变量。所以可以理解为 UserService 是有状态的,count 会被多个线程共享、修改。
public class MyServlet extends HttpServlet {
// 是否安全? 不安全:UserService 里面存在 count 成员变 // 量,可以被其他线程共享
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
// 记录调用次数:要对count做一些保护
private int count = 0;
public void update() {
// ...
count++;
}
}
例3
解决办法:可以做成环绕通知,把 start 变为局部变量
@Aspect
@Component
public class MyAspect {
// 是否安全? 不安全:spring默认是单例,单例就是要被共享的,里面的成员变量也是要被共享的。
private long start = 0L;
@Before("execution(* *(..))")
public void before() {
start = System.nanoTime();
}
@After("execution(* *(..))")
public void after() {
long end = System.nanoTime();
System.out.println("cost time:" + (end-start));
}
}
例4
方法内的局部变量,是线程安全的, 成员变量,要看他是否有状态,是否有其他方法会对它进行修改。
public class MyServlet extends HttpServlet {
// 是否安全 安全:虽然 UserServiceImpl 中有一个成员变量,但是它是私有的,没有其他地方能修改它
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
// 是否安全 安全:虽然是成员变量,会被多个线程所共享,但是 UserDao 中没有可更改的属性,里面没有成员变量,也叫无状态的。
private UserDao userDao = new UserDaoImpl();
public void update() {
userDao.update();
}
}
public class UserDaoImpl implements UserDao {
//方法内的局部变量,是线程安全的
public void update() {
String sql = "update user set password = ? where username = ?";
// 是否安全 安全
try (Connection conn = DriverManager.getConnection("","","")){
// ...
} catch (Exception e) {
// ...
}
}
}
例5
public class MyServlet extends HttpServlet {
// 是否安全,否:里面有成员变量,是有状态的
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
// 是否安全:否:UserDaoImpl 是成员变量,会被多个线程所共享
private UserDao userDao = new UserDaoImpl();
public void update() {
userDao.update();
}
}
public class UserDaoImpl implements UserDao {
// 是否安全 ,否:成员变量会被多个线程所共享
private Connection conn = null;
public void update() throws SQLException {
String sql = "update user set password = ? where username = ?";
conn = DriverManager.getConnection("","","");
// ...
conn.close();
}
}
例6
public class MyServlet extends HttpServlet {
// 是否安全,是
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
public void update() {
//局部变量,每次都新建一个对象,UserDaoImpl里面的成员变量每次都是新建,不会被共享
UserDao userDao = new UserDaoImpl();
userDao.update();
}
}
public class UserDaoImpl implements UserDao {
// 是否安全,是:但是不推荐这种写法,还是把 Connection 作为局部变量最为安全
private Connection = null;
public void update() throws SQLException {
String sql = "update user set password = ? where username = ?";
conn = DriverManager.getConnection("","","");
// ...
conn.close();
}
}
例7
局部变量对象引用泄漏
public abstract class Test {
public void bar() {
// 是否安全,否:虽然是局部变量,但是它会传个其他方法,比如这里传递给了 foo() 方法,foo() 中可能对它进行修改
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
foo(sdf);
}
public abstract foo(SimpleDateFormat sdf);
public static void main(String[] args) {
new Test().bar();
}
}
其中 foo 的行为是不确定的,可能导致不安全的发生,被称之为外星方法
public void foo(SimpleDateFormat sdf) {
String dateStr = "1999-10-11 00:00:00";
for (int i = 0; i < 20; i++) {
new Thread(() -> {
try {
sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}
请比较 JDK 中 String 类的实现,不可变,并且类是 final 的,确保没有子类去修改它
例8
private static Integer i = 0;
public static void main(String[] args) throws InterruptedException {
List<Thread> list = new ArrayList<>();
for (int j = 0; j < 2; j++) {
Thread thread = new Thread(() -> {
for (int k = 0; k < 5000; k++) {
synchronized (i) {
i++;
}
}
}, "" + j);
list.add(thread);
}
list.stream().forEach(t -> t.start());
list.stream().forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
log.debug("{}", i);
}
作者:天下没有收费的bug
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现