目录
1. 什么是线性安全问题
同时满足以下两个条件,则会出现线程安全问题。
- 存在多线程并发访问
- 存在可修改的共享数据
当多个线程同时修改同一个共享数据时,后修改的数据会将先修改的数据覆盖,对数据先进行修改的用户读取到的不是自己修改后的数据,这就是线程安全问题
2. JVM中什么数据可能会有线性安全问题
栈内存数据分析
栈内存是多例的,即 JVM 会为每个线程创建一个栈,所以其中的数据不是共享的。另 外,方法中的局部变量存放在 Stack 的栈帧中,方法执行完毕,栈帧弹栈,局部变量消失。 局部变量是局部的,不是共享的。所以栈内存中的数据不存在线程安全问题。
堆内存数据分析
一个 JVM 中只存在一个堆内存,堆内存是共享的。被创建出的对象是存放在堆内存的, 而存放在堆内存中的对象,实际就是对象成员变量的值的集合。即成员变量是存放在堆内存 的。堆内存中的数据是多线程共享的,也就是说,堆内存中的数据是存在线程安全问题的。
方法区数据分析
一个 JVM 中只存在一个方法区。静态变量与常量存放在方法区,方法区是多线程共享 的。常量是不能被修改的量,所以常量不存在线程安全问题。静态变量是多线程共享的,所 以静态变量存在线程安全问题总结:静态变量、成员变量是多线程共享的,可能会出现线性安全问题
3. Servlet为什么会出现安全问题
Servlet 是在单例多线程环境下运行的。其运行可能会出现线程安全问题
4. 线程安全问题举例
LoginServlet.java
package com.orbit.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
//定义成员变量,成员变量是可修改的,故线程不安全
private String username;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
username = request.getParameter("uname");
PrintWriter out = response.getWriter();
out.print("username = " + username);
}
}
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="login" method="post">
用户名<input type="text" name="uname"/><br/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
使用tomcat的debug模式运行项目:
5. 解决方案
对于 Servlet 的使用,一般是不声明成员变量的。若项目中要求必须要声明成员变量,则只能通过线程同步机制 synchronized 避免线程安全问题.
方案①——将成员变量修改为局部变量
LoginServlet.java
package com.orbit.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//修改为局部变量
String username = request.getParameter("uname");
PrintWriter out = response.getWriter();
out.print("username = " + username);
}
}
使用tomcat的debug模式运行项目:
方案②——将对 username 的操作语句均放入同步语句块 synchronized 中,让所有请求进行串行化排队执行
最好不要使用线程同步机制。因为一旦操作进入串行化的排队状态,将大大降低程序的执行效率。
LoginServlet.java
package com.orbit.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
//定义成员变量,成员变量是可修改的,故线程不安全
private String username;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//添加同步锁:缺点为一次只能一个用户通过
synchronized (this) {
username = request.getParameter("uname");
PrintWriter out = response.getWriter();
out.print("username = " + username);
}
}
}
结果与方案①一致(图略)
6. 线程不安全时的作用------统计次数
LoginServlet.java
package com.orbit.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
//定义成员变量,成员变量是可修改的,故线程不安全
private int count;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//该servlet每被访问一次,count就加一
count++;
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("该页面已被访问" + count + "次");
}
}