在Java中,处理多线程并发访问共享资源时,经常会遇到数据一致性和线程安全的问题。以买票问题为例,假设有一个售票系统,多个售票窗口(线程)同时售卖同一张票池中的票。如果不加以控制,就可能出现票数被重复计算或超卖的情况。
为了解决这个问题,我们可以使用synchronized
关键字来确保同一时刻只有一个线程能够执行特定的代码段(即临界区)。以下是几种使用synchronized
解决买票问题的方法:
方法1:同步方法
将售卖票的方法声明为synchronized
,这样在任何时候,只有一个线程能够进入这个方法。
public class TicketSeller {
private int tickets = 100;
public synchronized void sellTicket() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " 卖了一张票,剩余:" + --tickets);
} else {
System.out.println("票已售罄");
}
}
// 假设这里有其他方法和字段
}
方法2:同步代码块
如果只需要对方法中的某一部分进行同步,可以使用synchronized
代码块,这样可以减少同步的范围,提高程序的性能。
public class TicketSeller {
private int tickets = 100;
public void sellTicket() {
synchronized (this) { // 使用当前实例作为锁
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " 卖了一张票,剩余:" + --tickets);
} else {
System.out.println("票已售罄");
}
}
}
// 假设这里有其他方法和字段
}
方法3:使用锁对象
当多个不相关的对象需要互相协作进行同步时,可以使用一个公共的锁对象。
public class TicketSeller {
private int tickets = 100;
private final Object lock = new Object(); // 锁对象
public void sellTicket() {
synchronized (lock) { // 使用专门的锁对象
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " 卖了一张票,剩余:" + --tickets);
} else {
System.out.println("票已售罄");
}
}
}
// 假设这里有其他方法和字段
}
注意事项
- 过度使用
synchronized
可能会导致性能问题,因为它会限制并发性。 synchronized
方法或代码块中的锁对象必须是可及的(accessible),即对于其他需要同步的线程来说,它必须是可见的。- 尽量避免在
synchronized
块中进行复杂的操作或调用外部方法,因为这会延长锁的持有时间,增加死锁的风险。 - 在Java中,还有其他并发控制机制,如
ReentrantLock
,它提供了比synchronized
更灵活的锁定机制,但使用时需要注意正确地释放锁,以避免死锁。