《Java多线程编程核心技术(第2版)》 —1.2.9 Servlet技术造成的非线程安全问题与解决方案
1.2.9 Servlet技术造成的非线程安全问题与解决方案
非线程安全问题主要指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序执行流程。下面通过一个示例来学习如何解决因Servlet技术造成的非线程安全问题。
创建t4_threadsafe项目,以实现非线程安全的环境。LoginServlet.java代码如下:
package controller;
//本类模拟成一个Servlet组件
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
public static void doPost(String username, String password) {
try {
usernameRef = username;
if (username.equals("a")) {
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username=" + usernameRef + " password="
+ password);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
线程ALogin.java代码如下:
package extthread;
import controller.LoginServlet;
public class ALogin extends Thread {
@Override
public void run() {
LoginServlet.doPost("a", "aa");
}
}
线程BLogin.java代码如下:
package extthread;
import controller.LoginServlet;
public class BLogin extends Thread {
@Override
public void run() {
LoginServlet.doPost("b", "bb");
}
}
运行类Run.java代码如下:
public class Run {
public static void main(String[] args) {
ALogin a = new ALogin();
a.start();
BLogin b = new BLogin();
b.start();
}
}
程序运行结果如图1-24所示。
运行结果是错误的,在研究问题的原因之前,首先要知道两个线程向同一个对象的public static void doPost(String username, String password)方法传递参数时,方法的参数值不会被覆盖,方法的参数值是绑定到当前执行线程上的。
执行错误结果的过程如下。
1)在执行main()方法时,执行的结构顺序如下:
ALogin a = new ALogin();
a.start();
BLogin b = new BLogin();
b.start();
这样的代码被顺序执行时,很大概率是ALogin线程先执行,BLogin线程后执行,因为ALogin线程是首先执行start()方法的,并且在执行a.start()之后又执行BLogin b = new BLogin(),实例化代码需要耗时,更增大了ALogin线程先执行的概率。
但如果BLogin线程的确得到了先执行的机会,那么运行结果有可能出现两种:
运行结果1:
b bb
a aa
运行结果2:
a bb
a aa
2)ALogin线程执行了public static void doPost(String username, String password)方法,对username和password传入值a和aa。
3)ALogin线程执行usernameRef = username语句,将值a赋值给usernameRef。
4)ALogin线程执行if (username.equals("a"))代码符合条件,执行Thread.sleep(5000)停止运行5s。
5)BLogin线程也执行public static void doPost(String username, String password)方法,对username和password传入值b和bb。
6)由于LoginServlet .java是单例的,只存在1份usernameRef和passwordRef变量,所以ALogin线程对usernameRef赋的a值被BLogin线程的值b所覆盖,usernameRef值变成b。
7)BLogin线程执行if (username.equals("a"))代码不符合条件,不执行Thread.sleep(5000),而继续执行后面的赋值语句,将passwordRef值变成bb。
8)BLogin线程执行输出语句,输出b和bb的值。
9)5s之后,ALogin线程继续向下运行,参数password的值aa是绑定到当前线程的,所以不会被BLogin线程的bb值所覆盖,将ALogin线程password的值aa赋值给变量passwordRef,而usernameRef还是BLogin线程赋的值b。
10)ALogin线程执行输出语句,输出b和aa的值。
以上就是对运行过程的分析。
需要注意的是,如果代码改成:
ALogin a = new ALogin();
BLogin b = new BLogin();
a.start();
b.start();
则BLogin线程先执行的比重会加大,并且输出如下两种结果的概率较大。
运行结果1:
a bb
a aa
运行结果2:
b bb
b aa
解决“非线程安全”问题可以使用synchronized关键字,更改代码如下:
synchronized public static void doPost(String username, String password) {
try {
usernameRef = username;
if (username.equals("a")) {
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username=" + usernameRef + " password="
+ password);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
程序运行结果如图1-25所示。
在Web开发中,Servlet对象本身就是单例的,所以为了不出现非线程安全问题,建议不要在Servlet中出现实例变量。
- 点赞
- 收藏
- 关注作者
评论(0)