一、需求分析
利用多线程的思想模拟三个窗口售票员卖30张票的功能:
我们采用线程对象来模拟售票窗口,实现多个窗口同时卖票, 采用 Runnable 接口子类来模拟票数。
二、代码实现
1、继承 Thread 类的方式
class TicketWindow extends Thread {
// 数量
private int ticket = 30;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket-- + "张票," + "剩余" + ticket + "张票");
} else {
System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
break;
}
}
}
}
public class SellTicketTest1 {
public static void main(String[] args) {
TicketWindow tw1 = new TicketWindow();
TicketWindow tw2 = new TicketWindow();
TicketWindow tw3 = new TicketWindow();
tw1.setName("窗口1");
tw2.setName("窗口2");
tw3.setName("窗口3");
tw1.start();
tw2.start();
tw3.start();
}
}
运行结果:
通过代码执行结果可以看出,在运行的过程中出现了错误。因为窗口总共有 30 张票,可是窗口1、窗口2、窗口3各自卖出了30 张票,等于卖出去 90 张票,说明票没有共享。
解决继承 Thread 类,线程不共享数据的问题:
加 static 关键字, 将票声明为静态变量,让三个售票窗口线程共享票。
class TicketWindow extends Thread {
// 数量
private static int ticket = 30;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket-- + "张票," + "剩余" + ticket + "张票");
} else {
System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
break;
}
}
}
}
public class SellTicketTest1 {
public static void main(String[] args) {
TicketWindow tw1 = new TicketWindow();
TicketWindow tw2 = new TicketWindow();
TicketWindow tw3 = new TicketWindow();
tw1.setName("窗口1");
tw2.setName("窗口2");
tw3.setName("窗口3");
tw1.start();
tw2.start();
tw3.start();
}
}
运行结果:
2、使用实现 Runnable 接口的方式
一个类通过继承 Thread 来实现多线程的话,则不适合多个线程共享资源,而通过实现 Runnable 就可以很轻松做到这一点。
通过实现 Runnable 接口,我们不需要加 static 关键字把车票声明为静态的,也可以实现数据共享。实现 Runnable 接口的方式创建多线程,只产生了一个 TicketWindow2 对象,一个对象里边有一个属性,这样三个线程同时在操作一个属性,运行同一个 run 方法。
在实际开发中,推荐使用实现 Runnable 方式。
class TicketWindow2 implements Runnable {
private int ticket = 30;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket-- + "张票," + "剩余" + ticket + "张票");
} else {
System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
break;
}
}
}
}
public class SellTicketTest2 {
public static void main(String[] args) {
TicketWindow2 tw = new TicketWindow2();
for (int i = 1; i < 4; i++) {
Thread t = new Thread(tw,"窗口"+i);
t.start();
}
}
}
以上两种方式存在线程安全问题:
打印车票时,会出现重票和错票。造成这个问题的原因是一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在安全问题。
如何解决:
当线程1在操作 ticket 的时候,其他线程不能参与进来。直到线程1操作完 ticket 时,其他线程才可以开始操作 ticket。这种情况即使线程1出现了阻塞,也不能被改变。
在 Java 中,我们通过 同步机制,来解决线程的安全问题。
3、解决卖票过程中的线程安全问题
class TicketWindow3 implements Runnable{
private static int ticket = 30;
@Override
public void run() {
while (true) {
synchronized (this) {
if (ticket > 0) {
try {
// 线程出现阻塞
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket-- + "张票," + "剩余" + ticket + "张票");
} else {
System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
break;
}
}
}
}
}
public class SellTicketTest3 {
public static void main(String[] args) {
TicketWindow3 tw = new TicketWindow3();
for (int i = 1; i < 4; i++) {
Thread t = new Thread(tw,"窗口"+i);
t.start();
}
}
}
运行结果: