Java多窗口卖票问题详解

在练习Java多线程的过程中,通常都会通过多窗口卖票这个问题来详细逐渐解析多线程的线程同步,其中涉及到同步代码块同步方法互斥锁

铁道部发布了一个售票任务,销售1000张票,要求有10个窗口来进行销售,请编写多线程程序来模拟这个效果。

1 第一步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Test4Thread extends Thread {
private static int tickets = 1000;

@Override
public void run() {
while (tickets > 0) {
System.out.println(this.getName() + "正在销售第" + (tickets--) + "张票!");
}
}
}

public class Test4 {
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
new Test4Thread().start();
}
}
}

运行代码发现存量重复卖票问题,因为多个线程同时运行时,就会出现多个线程获取到的变量值是相同的,导致出现重复卖票问题。

重复卖票问题

2 第二步

1
2
3
4
5
6
7
8
@Override
public void run() {
while (tickets > 0) {
synchronized ("lock") {
System.out.println(this.getName() + "正在销售第" + (tickets--) + "张票!");
}
}
}

这里虽然在循环体内部增加了同步代码块,但是可能在票数为1时,可能会出现多个线程进入循环体排队运行代码块的情况,这样就会出现买负数票的问题。

出现卖负数票问题

3 第三步

1
2
3
4
5
6
7
8
9
10
11
public void run() {
while (true) {
synchronized ("lock") {
if (tickets>0) {
System.out.println(this.getName() + "正在销售第" + (tickets--) + "张票!");
}else {
break;
}
}
}
}

线程类的run方法经过修改后代码结果符合预期。

最终运行结果

4 最终代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Test4Thread extends Thread {
private static int tickets = 1000;

@Override
public void run() {
while (true) {
synchronized ("lock") {
if (tickets>0) {
System.out.println(this.getName() + "正在销售第" + (tickets--) + "张票!");
}else {
break;
}
}
}
}
}

public class Test4 {
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
new Test4Thread().start();
}
}
}

5 利用同步方法来处理卖票问题:

Java除了利用同步代码块之外,还可以利用同步方法来处理问题,利用同步方法时仍要要注意调用同步方法是他一个对象,所以继承的线程类需要增加static关键字,同样需要通过同步方法的返回值来判断线程票是否卖完。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Test5Thread extends Thread {
private static int tickets = 1000;

@Override
public void run() {
while (test()) {
}
}

public static synchronized boolean test() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在销售第" + (tickets--) + "张票!");
return true;// 表示票还没有卖完
} else {
System.out.println("票已经卖完了");
return false;// 表示票已经卖完
}
}
}

public class Test5 {
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
new Test5Thread().start();
}
}
}

6 利用互斥锁来解决卖票问题:

除了利用同步代码块和同步方法外,在Java里面还可以利用互斥锁来处理,在用互斥锁处理的过程中,为了避免出现死锁问题,需要利用try-finally来互斥锁,将结果代码放在finally代码块中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Test6Thread extends Thread {
private static int tickets = 1000;
private static Lock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
lock.lock();
try {
if (tickets > 0) {
System.out.println(this.getName() + "正在销售第" + (tickets--) + "张票!");
} else {
System.out.println("票已经卖完了");
break;
}

} finally {
lock.unlock();
}

}
}
}
public class Test6 {
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
new Test6Thread().start();
}
}
}
-------------本文结束感谢您的阅读-------------