卖票案例解析
创建Runnable接口的实现类RunnableImpl。内联代码片
。
// 创建Runnable接口的实现类
public class RunnableImpl implements Runnable {
//定义一个多线程共享的票源
private int ticket = 100;
//重写run方法,设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//先判断票是否存在
if (ticket>=0){
//提高线程安全问题出现概率,设置睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票<--");
ticket--;
}
if (ticket==0){
break;
}
}
}
}
创建测试类Demo01Ticket。内联代码片
。
// 测试类,模拟卖票,开三个窗口同时卖票。
public class Demo01Ticket {
public static void main(String[] args) {
//创建一个Runnable接口实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread对象,构造方法中传递Runnable接口的实现类对象
Thread t1 = new Thread(run,"窗口一");
Thread t2 = new Thread(run,"窗口二");
Thread t3 = new Thread(run,"窗口三");
//调用start方法开启多线程
t1.start();
t2.start();
t3.start();
}
}
执行结果部分。 内联代码片
。
窗口一-->正在卖第2张票<--
窗口二-->正在卖第1张票<--
窗口三-->正在卖第0张票<--
窗口一-->正在卖第-1张票<--
线程安全原理
以上代码中多线程访问了共享的数据,会产生线程安全问题,下面我们来分析一下其原理。
从Demo01Ticket.java中可以看出,开启了3个线程t1、t2、t3,3个线程一起抢夺cpu的执行权,谁先抢到谁执行。
(1)抢占期间
假设t1先抢到:
t1线程抢到了cup的执行权,进入到run方法中执行,执行到if语句,就会失去cup执行权,进入睡眠状态。
其次假设t3先抢到:
t3线程抢到了cup的执行权,进入到run方法中执行,执行到if语句,就会失去cup执行权,进入睡眠状态。
最后假设t2先抢到:
t2线程抢到了cup的执行权,进入到run方法中执行,执行到if语句,就会失去cup执行权,进入睡眠状态。
(2)睡醒期间
假设t3先睡醒:
抢到了cpu执行权,继续执行进行卖票操作,假设当前ticket只有一张票,则输出“Thread-3 -->正在卖第1张票”这是ticket执行ticket- -操作;ticket = 0,继续判断0>0则不执行。
其次假设t2先睡醒:
抢到了cpu执行权,继续执行进行卖票操作,则输出“Thread-2 -->正在卖第0张票”这是ticket执行ticket- -操作;ticket = -1,继续判断-1>0则不执行。
最后假设t1先睡醒:
抢到了cpu执行权,继续执行进行卖票操作,则输出“Thread-1 -->正在卖第-1张票”这是ticket执行ticket- -操作;ticket = -2,继续判断-2>0则不执行。
总结:
从以上分析可以看出,多线程访问了共享的数据,会产生线程安全问题。
(下一篇解决线程安全问题的几种方式)