线程安全问题的本质
在多线程环境下,当多个线程同时操作同一个共享数据时,如果缺乏有效的同步机制,就容易出现线程安全问题。简单来说,就是一个线程还没执行完对数据的修改,另一个线程就介入并读取或修改了该数据,导致最终结果与预期不符。
synchronized 的解决方案
Java 提供了 synchronized 关键字来保证代码块的原子性,主要有两种用法:
- 同步代码块:指定一个对象作为锁。
synchronized (lockObject) { // 需要同步的代码 } - 同步方法:将方法修饰为
synchronized。此时锁对象默认为当前实例(this)。public synchronized void method() { // 需要同步的代码 }
这两种方式本质上是等价的,synchronized(this) 包裹的代码块效果等同于将该方法声明为同步方法。
底层原理简析
synchronized 的实现依赖于对象监视器(Monitor)。当一个线程进入同步代码块时,它会尝试获取对象的锁。如果锁被占用,其他线程会被阻塞等待;一旦持有锁的线程执行完毕释放锁,后续等待的线程才能继续执行。
这就像公共厕所的门锁一样,只有拿到钥匙(持有锁)的人才能进去,其他人必须排队等待。通过这种互斥机制,确保了同一时刻只有一个线程能访问临界区资源。
加锁的最佳实践
虽然 synchronized 能有效解决问题,但过度加锁会影响性能。建议遵循以下原则:
- 精准加锁:只锁定真正操作共享资源的代码段,不要扩大范围。
- 避免死锁:注意锁的嵌套顺序,防止多个线程互相等待对方释放锁。
- 权衡利弊:优点是解决了线程安全问题;缺点是增加了上下文切换和等待开销,消耗系统资源。
实战示例
下面是一个经典的'多窗口卖票'场景演示。三个线程模拟三个售票窗口,共同出售 1000 张票。如果不加锁,可能会出现超卖现象。
public class TicketDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = (ticket);
(ticket);
(ticket);
t1.start();
t2.start();
t3.start();
}
}
{
;
{
(num > ) {
() {
{
Thread.sleep();
System.out.println(Thread.currentThread().getName() + + num--);
} (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

