在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更灵活的锁定机制,但使用时需要注意正确地释放锁,以避免死锁。