xl_echo编辑整理



之前的文章我们讲到了,四个电影院窗口同时出售50张彩票的问题。在实现的过程中,我们使用Tread继承,达到了需求的效果,但是也提出了一部分问题。这里我们先使用Runnable进行改造之前的程序,实现效果,然后在来阐述之前的问题。


实现Tread完成需求代码如下:

package com.example.mybatisplusdemo.test;

/**
* @Author xl_echo
* @Date 2018/8/7 下午1:54
**/
public class Window extends Thread {

//售票窗口
private final String name;

//有50张电影票
private static final int MAXTicket = 10000;

public Window(String name) {
this.name = name;
}

private static int index = 1;

@Override
public void run(){
while (index <= MAXTicket){
System.out.println("窗口" + name + "当前是第" + (index++) + "张票");
}
}

public static void main(String[] args) {
Window window1= new Window("1号窗口");
window1.start();
Window window2= new Window("2号窗口");
window2.start();
Window window3= new Window("3号窗口");
window3.start();
Window window4= new Window("4号窗口");
window4.start();
}

}

使用Runnable改造,改造后代码如下

package com.example.mybatisplusdemo.test;

/**
* @Author xl_echo
* @Date 2018/8/8 上午10:53
**/
public class Test implements Runnable {

private int index = 1;

private final static int MAX = 50;

@Override
public void run() {

while (index <= MAX){
System.out.println("窗口" + Thread.currentThread() + "当前是第" + (index++) + "张票");
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}

public static void main(String[] args){
final Test test = new Test();

Thread windows1 = new Thread(test, "一号窗口");
Thread windows2 = new Thread(test, "二号窗口");
Thread windows3 = new Thread(test, "三号窗口");
Thread windows4 = new Thread(test, "四号窗口");

windows1.start();
windows2.start();
windows3.start();
windows4.start();

}

}

输出结果这里就不展示了,因为这里的结果和之前程序的结果是一样的,如果index增大的时候一样会出现线程安全问题。

可能有朋友会发现我们没有使用static修饰index,会不会是没有修饰造成的安全问题,其实不然。比如,有一个线程拿到了index,这个时候准备执行方法,刚好另外一个线程也拿到了index,同时也准备执行方法,这个时候就会造成两个窗口卖了同一张票。2而我们的输出就出现了一下图片中的输出相同票数的情况。当我们的某一个线程拿到了最后一张票的时候,index=50了,刚好该线程进入了休眠。然后其他两个线程进入,也获取到了index发现并大于max,这个时候就会执行卖票,当这边后进入的程序执行完成,前面的程序再次唤醒执行,就会出现卖了51张票。

解决办法:使用synchronized关键字。

  • 但是这个关键字加在什么地方呢?

比如:加在run()方法上面,你会发现基本都是一号窗口买完了所有的票。因为同步锁在第一条线程进入后,就锁死了该方法的执行,必须要第一条线程执行完成该方法才能够进入第二线程,所以等第二条进入的时候就会发现index已经大于50了,自然输出结果就是一条线程执行完了所有出票。

public synchronized void run() {...}

实现Runnable解决多线程数据安全问题_锁

比如:加在while循环里面,使用​​synchronized(this){...}​​括上方法里面的所有代码,这个时候你会发现,基本每次都多出了三张票。因为:while判断的下一步有四条线程执行,到最后一张票的时候,所有线程都拿到了,并且由于锁的关系,每条线程都执行一次,所以这个时候就多卖了三张。

while (index <= MAX) {
synchronized (this) {
...
}
}

实现Runnable解决多线程数据安全问题_多线程_02

以上程序最终的解决办法:

将同步锁加在休眠上面,我们会发现我们效果达到了,线程安全问题解决了

synchronized (this) {
Thread.sleep(100);
}

实现Runnable解决多线程数据安全问题_Runnable_03

这是什么原因呢?

因为我们加在​​Thread.sleep(100);​​ 上面的时候,我们的四个线程只有有一个进入休眠,其他的就会进入等待,刚好这时候进入休眠等待的线程就拦截了后的线程进行下一步操作。所以当我们的线程进入休眠的时候,前面的index叠加就已经完成了。

当我们的index叠加到46的时候,执行输出和index再次叠加,刚好每条线程叠加一次,线程进入休眠,后面的想再次叠加也会被拦截。当最前面的线程唤醒之后,后面的线程都已经执行完成了,index自然是要比MAX大。

当然这里也提出一个疑问?大家可以一起思考

那就是index叠加到46的时候,再次循环,第一条线程将index叠加为47,进入休眠,后面的线程在休眠时间内还没有完成最后的三张票出售。这个时候就会不会出现数据安全问题?欢迎大家跟我联系。