互斥锁与synchronized关键字

多线程环境下,确保数据一致性是一个至关重要的问题。其中一个常见的挑战是防止多个线程同时对共享资源进行修改,这可能导致数据竞争、不一致或崩溃。为了应对这一问题,Java提供了几种机制来实现互斥锁(Mutual Exclusion),其中最常用的之一就是java.util.concurrent.atomic.Atomic lockssynchronized关键字。

什么是互斥锁?

互斥锁的英文是 mutual exclusion,字面意思是“相互排除”,其核心思想就是在共享资源被修改时,禁止其他线程继续操作。这种机制可以确保只有单个线程在执行某个操作期间才能获得锁,其他线程必须等待直到当前线程释放锁。

互斥锁广泛应用于以下场景:

  • 用户注册系统:防止多个用户同时尝试注册相同用户名
  • 文件操作:防止多个程序对同一文件进行修改
  • 数据库操作:确保数据完整性

Java中的synchronized关键字

Java语言中,synchronized是一个关键字,它用于实现互斥锁。当一个方法被标记为synchronized时,Java会自动在该方法开始
和结束的地方创建并管理一个互斥锁。这样,任何尝试同时调用这个方法的线程都会被阻止,直到当前线程完成该方法的所有操作。

synchronized关键字的工作原理

  1. 锁获取:当第一个线程调用带有synchronized关键字的方法时,Java会自动获取锁。
  2. 执行方法:允许该线程执行所有被锁保护的语句。
  3. 锁释放:一旦该方法完成执行,当前线程会自动释放锁。
  4. 等待释放:其他试图调用同一个方法但未被锁保护的线程会被阻塞,直到当前线程释放锁。

使用 synchronized的一个示例

以下是一个简单的用户注册系统的示例:

synchronized (userAccount.intern()) {
//其他操作
}

在这个示例中:

  • 使用synchronized关键字,确保只有单个线程可以修改数据库中的用户信息。
  • 字符串池:userAccount.intern() 返回字符串池中与 userAccount 内容相同的字符串实例。如果字符串池中不存在该字符串,则将其添加到池中并返回其引用。
  • 同步锁:synchronized 关键字用于确保同一时间只有一个线程可以执行被同步的代码块。当多个线程尝试注册同一个用户账户时,它们都会尝试获取同一个锁对象(即 userAccount.intern() 返回的对象),从而保证这些线程不会同时进入同步代码块。

编写一个正确的synchronized示例

编写一个更复杂的例子,比如并发登录检查:

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent locksnonatomicLocks;

public class ConcurrentLoginCheck {
private AtomicInteger loginCount = new AtomicInteger(0);

public void checkLogin(String username) {
synchronized checkLogin Method {
if (loginCount.get() >= 3) { // 用户登录次数超过限制
System.out.println("账户已被冻结,建议联系管理员!");
return;
}

loginCount.set(loginCount.get() + 1); // 增加登录次数
System.out.println("用户:" + username + "的登录次数增加到:" + loginCount.get());
}
}

public void logout(String username) {
synchronized checkLogin Method {
if (loginCount.get() >= 3) {
return; // 用户已被冻结,不能再登录
}

loginCount.set(loginCount.get() - 1); // 减去登录次数
System.out.println("用户:" + username + "已退出登录!");
}
}
}

在这个示例中:

  • checkLoginlogout方法都使用了synchronized关键字来保护对loginCount计数器的访问。
  • 这样可以防止多个线程同时修改同一个计数器,确保数据的一致性。

注意事项

  1. 锁获取的时间:在频繁竞争的情况下,过度使用互斥锁可能会导致性能问题。因此,在决定是否需要使用互斥锁时,应该权衡性能和功能需求。
  2. 条件等待:当没有其他线程需要互斥锁时,可以考虑使用更轻量的条件变量(如Concurrent Boolean Locks)来代替互斥锁。这可以提高系统的性能。