1199 字
6 分钟
聊聊线程同步

聊聊线程同步#

所谓同步,即协同步调,按预定的先后次序访问共享资源,以免造成混乱。 线程同步是多线程编程中的一个核心概念,它涉及到在多线程环境下如何安全地访问和修改共享资源的问题。 当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作。 如果多个线程同时读写某个共享资源(如变量、文件等),而没有适当的同步机制,就可能导致数据不一致、数据损坏等问题的出现。 线程同步的实现方式有 6 种:互斥量、读写锁、条件变量、自旋锁、屏障、信号量。

  • 互斥量:互斥量(mutex)是一种最基本的同步手段,本质上是一把锁,在访问共享资源前先对互斥量进行加锁,访问完后再解锁。对互斥量加锁后,任何其他试图再次对互斥量加锁的线程都会被阻塞,直到当前线程解锁。
  • 读写锁读写锁有三种状态,读模式加锁、写模式加锁和不加锁;一次只有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。非常适合读多写少的场景。
  • 条件变量条件变量是一种同步手段,它允许线程在满足特定条件时才继续执行,否则进入等待状态。条件变量通常与互斥量一起使用,以防止竞争条件的发生。
  • 自旋锁:自旋锁是一种锁的实现方式,它不会让线程进入睡眠状态,而是一直循环检测锁是否被释放。自旋锁适用于锁的持有时间非常短的情况。
  • 信号量:信号量(Semaphore)本质上是一个计数器,用于为多个进程提供共享数据对象的访问。

推荐阅读:牛客:可能是全网最全的线程同步方式总结了 在 Java 中,synchronized 关键字和 Lock 接口是用来实现线程同步的常用方式,我就以它俩来举例说明。

synchronized 关键字#

当一个线程访问某对象的 synchronized 方法或代码块时,其他线程对该对象的所有 synchronized 方法或代码块的访问将被阻塞,直到第一个线程完成操作。 synchronized 关键字就属于典型的互斥量,它保证了同一时间只有一个线程可以访问共享资源。

public class Counter {
private int count = 0;
// 使用synchronized方法保证线程安全
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}

在这个例子中,increment 方法和 getCount 方法都被标记为 synchronized。这意味着同一时间内只有一个线程可以执行这两个方法中的任意一个。 在 JVM 的早期版本中,synchronized 是重量级的,因为线程阻塞和唤醒需要操作系统的介入。但在 JVM 的后续版本中,对 synchronized 进行了大量优化,如偏向锁、轻量级锁和适应性自旋等,所以现在的 synchronized 并不一定是重量级的,其性能在许多情况下都很好,可以大胆地用。

Lock 接口#

Lock 接口提供了比 synchronized 关键字更灵活的锁操作。比如说我们可以用重入锁 ReentrantLock来实现同样的功能。

public class CounterWithLock {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 释放锁
}
}
public int getCount() {
return count;
}
}

increment 方法先上锁,然后尝试增加 count 的值,在完成操作后释放锁。这样就可以保证 count 的操作是线程安全的。 ReentrantLock 和 synchronized 都可以用来实现同步,但它们之间也存在一些区别:

  • ReentrantLock 是一个类,而 synchronized 是 Java 中的关键字
  • ReentrantLock 可以实现多路选择通知(可以绑定多个 Condition),而 synchronized 只能通过 wait 和 notify/notifyAll 方法唤醒一个线程或者唤醒全部线程(单路通知)
  • ReentrantLock 必须手动释放锁。通常需要在 finally 块中调用 unlock 方法以确保锁被正确释放。
  • synchronized 会自动释放锁,当同步块执行完毕时,由 JVM 自动释放,不需要手动操作。
  • ReentrantLock: 通常提供更好的性能,特别是在高竞争环境下。
  • synchronized: 在某些情况下,性能可能稍差一些,但随着 JDK 版本的升级,性能差距已经不大了。
聊聊线程同步
作者
强人自传
发布于
2024-11-22
许可协议
CC BY-NC-SA 4.0