【探索】初识多线程之非线程安全

初识多线程之非线程安全

非线程安全问题主要指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况。
实例变量共享造成的非线程安全

例如:在实现投票功能的设计时,多个线程同时处理同一个人的票数

创建:类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

分析一下造成这种结果的原因:

  1. ALogin线程执行了doPost方法,对username和password传入值a和aa
  2. ALogin线程执行了将username的值赋给了usernameRef
  3. ALogin线程满足if的条件,ALogin线程暂停运行5s
  4. 此时,Blogin线程执行了doPost方法,对username和password传入了b和bb
  5. 由于LoginServlet是单例的,只存在一份usernameRef和passwordRef变量,所以ALogin线程对usernameRef的a值被Blogin线程的b所覆盖,usernameRef值变成b
  6. 当Blogin线程执行到if时,不满足条件,继续执行,将passwordRef变成了bb
  7. Blogin线程执行输出,输出b和bb的值
  8. 5s之后,ALogin线程继续向下执行,参数password的值是aa是绑定到当前线程的,所以不会被Blogin的bb所覆盖,password将aa覆给passwordRef,但是usernameRef被Blogin覆盖成了bb
  9. 所以最后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中出现实例变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值