Java 多线程同步:模拟售票、年会入场案例详解
Java 多线程同步:模拟售票、年会入场案例详解
在多线程编程中,线程安全问题是需要重点关注的。当多个线程同时访问共享资源时,如果不进行同步处理,就会出现数据错误。本文将通过模拟售票和年会入场两个典型案例,详细介绍 Java 多线程同步机制,帮助你理解并解决线程安全问题。
1. 模拟售票系统
**题目:**请编写程序,不使用任何同步技术,模拟三个窗口同时卖 100 张票的情况,运行并打印结果,观察到错误的数据,并解释出现错误的原因。
代码(未同步)
public class TicketSale implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (tickets > 0) {
try {
Thread.sleep(100); // 模拟其他操作耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + '卖出了第' + tickets-- + '张票');
}
}
public static void main(String[] args) {
TicketSale ticketSale = new TicketSale();
new Thread(ticketSale, '窗口1').start();
new Thread(ticketSale, '窗口2').start();
new Thread(ticketSale, '窗口3').start();
}
}
运行结果:
窗口1卖出了第100张票
窗口1卖出了第98张票
窗口2卖出了第99张票
窗口1卖出了第97张票
窗口3卖出了第96张票
...
**错误原因:**多个线程同时操作 tickets 变量,导致数据错误。例如,当 tickets 为 99 时,多个线程可能同时读取到该值,并将其减 1,最终导致卖出的票数超过 100 张。
**解决方法:**使用同步技术来保证对 tickets 变量的访问是互斥的,即同一时间只有一个线程可以访问该变量。
同步代码块
public class TicketSale implements Runnable {
private int tickets = 100;
private final Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
if (tickets > 0) {
try {
Thread.sleep(100); // 模拟其他操作耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + '卖出了第' + tickets-- + '张票');
} else {
break;
}
}
}
}
public static void main(String[] args) {
TicketSale ticketSale = new TicketSale();
new Thread(ticketSale, '窗口1').start();
new Thread(ticketSale, '窗口2').start();
new Thread(ticketSale, '窗口3').start();
}
}
同步方法
public class TicketSale implements Runnable {
private int tickets = 100;
@Override
public synchronized void run() {
while (tickets > 0) {
try {
Thread.sleep(100); // 模拟其他操作耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + '卖出了第' + tickets-- + '张票');
}
}
public static void main(String[] args) {
TicketSale ticketSale = new TicketSale();
new Thread(ticketSale, '窗口1').start();
new Thread(ticketSale, '窗口2').start();
new Thread(ticketSale, '窗口3').start();
}
}
Lock 锁
public class TicketSale implements Runnable {
private int tickets = 100;
private final Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (tickets > 0) {
try {
Thread.sleep(100); // 模拟其他操作耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + '卖出了第' + tickets-- + '张票');
} else {
break;
}
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
TicketSale ticketSale = new TicketSale();
new Thread(ticketSale, '窗口1').start();
new Thread(ticketSale, '窗口2').start();
new Thread(ticketSale, '窗口3').start();
}
}
2. 模拟年会入场
**题目:**某公司组织年会,会议入场时有两个入口,在入场时每位员工都能获取一张双色球彩票,假设公司有 100 个员工,利用多线程模拟年会入场过程,并分别统计每个入口入场的人数,以及每个员工拿到的彩票的号码。
线程运行后打印格式如下:
1 编号为: 2 的员工 从后门 入场! 拿到的双色球彩票号码是:[17, 24, 29, 30, 31, 32, 07]
2 编号为: 1 的员工 从后门 入场! 拿到的双色球彩票号码是:[06, 11, 14, 22, 29, 32, 15]
3 //.....
4 从后门入场的员工总共: 13 位员工
5 从前门入场的员工总共: 87 位员工
代码(同步代码块)
import java.util.Arrays;
import java.util.Random;
public class DoubleColorBallUtil {
// 产生双色球的代码
public static String create() {
String[] red = {'01', '02', '03', '04', '05', '06', '07', '08', '09', '10',
'11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '2
4', '25', '26', '27', '28', '29', '30', '31', '32', '33'};
//创建蓝球
String[] blue = '01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16'.s
plit(',');
boolean[] used = new boolean[red.length];
Random r = new Random();
String[] all = new String[7];
for (int i = 0; i < 6; i++) {
int idx;
do {
idx = r.nextInt(red.length);//0‐32
} while (used[idx]);//如果使用了继续找下一个
used[idx] = true;//标记使用了
all[i] = red[idx];//取出一个未使用的红球
}
all[all.length - 1] = blue[r.nextInt(blue.length)];
Arrays.sort(all);
return Arrays.toString(all);
}
}
public class Entrance implements Runnable {
private static final int TOTAL_EMPLOYEES = 100;
private static final int FRONT_DOOR_EMPLOYEES = 87;
private static final int BACK_DOOR_EMPLOYEES = 13;
private static final Object lock = new Object();
private static int frontDoorCount = 0;
private static int backDoorCount = 0;
private final int id;
public Entrance(int id) {
this.id = id;
}
@Override
public void run() {
for (int i = 0; i < TOTAL_EMPLOYEES; i++) {
String lottery = DoubleColorBallUtil.create();
synchronized (lock) {
if (id == 1) {
System.out.printf('%d 编号为: %d 的员工 从后门 入场! 拿到的双色球彩票号码是:%s%n', i + 1, Thread.currentThread().getId(), lottery);
backDoorCount++;
} else {
System.out.printf('%d 编号为: %d 的员工 从前门 入场! 拿到的双色球彩票号码是:%s%n', i + 1, Thread.currentThread().getId(), lottery);
frontDoorCount++;
}
}
}
}
public static void main(String[] args) {
new Thread(new Entrance(1), '后门').start();
new Thread(new Entrance(2), '前门').start();
try {
Thread.sleep(1000); // 等待两个线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf('从后门入场的员工总共: %d 位员工%n', backDoorCount);
System.out.printf('从前门入场的员工总共: %d 位员工%n', frontDoorCount);
}
}
总结
本文通过售票、年会入场等经典案例,详细介绍了 Java 多线程同步机制,包括使用同步代码块、同步方法、Lock 锁解决线程安全问题,并附带完整代码示例。建议你动手尝试运行代码,加深对多线程同步的理解。
在实际开发中,选择合适的同步方式取决于具体的需求,需要根据实际情况进行判断。例如,如果需要在多个线程之间进行更复杂的协调,则可以使用 Lock 锁,它提供了更灵活的控制机制。
原文地址: https://www.cveoy.top/t/topic/oe9q 著作权归作者所有。请勿转载和采集!