初识多线程之非线程安全
非线程安全问题主要指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况。
实例变量共享造成的非线程安全
例如:在实现投票功能的设计时,多个线程同时处理同一个人的票数
创建:类MyThread
public class MyThread extends Thread{
private int count = 5;
@Override
public void run() {
super.run();
count--;
System.out.println("由"+this.currentThread().getName()+"计算,count= "+count);
}
}
类:MyThreadRun
public class MyThreadRun {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread a = new Thread(myThread,"A");
Thread b = new Thread(myThread,"B");
Thread c = new Thread(myThread,"C");
Thread d = new Thread(myThread,"D");
Thread e = new Thread(myThread,"E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
运行MyThreadRun类:
得到如图所示的运行结果
由A计算,count= 3
由C计算,count= 2
由B计算,count= 3
由E计算,count= 0
由D计算,count= 1
这一个运行结果说明了以下几个点:
1.线程是随机性的,代码的上下顺序是不会影响到线程的执行顺序的
2.执行start()的顺序不代表执行run()的顺序
3.多个线程同时访问一个实例变量,那么很大概率会出现非线程安全问题
实例变量共享造成的非线程安全问题的解决办法 : 多个线程之间进行同步操作,即用按顺序排队的方式进行操作
更改MyThread类的代码如下:
public class MyThread extends Thread{
private int count = 5;
@Override
synchronized public void run() { --->//使用synchronized 关键词对执行count的run方法进行上锁
super.run();
count--;
System.out.println("由"+this.currentThread().getName()+"计算,count= "+count);
}
}
Servlet技术造成的非线程安全以及解决办法
创建类LoginServlet,模拟
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
public static void doPost(String username,String password){
try {
usernameRef = username;
if("a".equals(username)){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username="+usernameRef+",password="+password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
创建ALogin类:
public class ALogin extends Thread{
@Override
public void run(){
LoginServlet.doPost("a","aa");
}
}
窗边BLogin类:
public class BLogin extends Thread{
@Override
public void run(){
LoginServlet.doPost("b","bb");
}
}
现在写一个测试类,来验证一下运行结果:
public class RunTest {
public static void main(String[] args) {
ALogin a = new ALogin();
a.start();
BLogin b = new BLogin();
b.start();
}
}
现在的运行结果为,很明显数据有错误,username为b的密码变成了a用户的密码
username=b,password=bb
username=b,password=aa
分析一下造成这种结果的原因:
- ALogin线程执行了doPost方法,对username和password传入值a和aa
- ALogin线程执行了将username的值赋给了usernameRef
- ALogin线程满足if的条件,ALogin线程暂停运行5s
- 此时,Blogin线程执行了doPost方法,对username和password传入了b和bb
- 由于LoginServlet是单例的,只存在一份usernameRef和passwordRef变量,所以ALogin线程对usernameRef的a值被Blogin线程的b所覆盖,usernameRef值变成b
- 当Blogin线程执行到if时,不满足条件,继续执行,将passwordRef变成了bb
- Blogin线程执行输出,输出b和bb的值
- 5s之后,ALogin线程继续向下执行,参数password的值是aa是绑定到当前线程的,所以不会被Blogin的bb所覆盖,password将aa覆给passwordRef,但是usernameRef被Blogin覆盖成了bb
- 所以最后ALogin线程的输出语句为usernameRef:b,【password:aa
servlet技术造成的非线程安全的解决办法:synchronized关键字
修改代码如下:
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
synchronized public static void doPost(String username,String password){
try {
usernameRef = username;
if("a".equals(username)){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username="+usernameRef+",password="+password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
最后的运行结果:
username=a,password=aa
username=b,password=bb
PS:在web开发中,servlet对象本身就是单例的,所以为了不出现非线程安全问题,建议不要在servlet中出现实例变量。