Chapter 2: Thread Safety
第二章,主要讲的是线程安全的问题,及解决方法,现在写的是如何去理解线程安全,下一篇写2.1 What is thread safety
Whenever more than one thread accesses a given state variable, and one of them might write to it, they all must coordinate their access to it using synchronization.
当多个线程访问同一个状态变量,并且其中一个线程可能对其进行写操作时,它们必须使用同步机制来协调对该变量的访问。
The primary mechanism for synchronization in Java is the synchronized keyword, which provides exclusive locking, but the term “synchronization” also includes the use of volatile variables, explicit locks, and atomic variables.
在Java中,同步的主要机制是使用
synchronized
关键字,它提供了(提供独占锁定/排他性的锁定)排它锁。但是,“同步”这个术语还包括使用volatile变量、显式锁和原子变量。
- volatile变量:
volatile
关键字用于声明变量,确保线程之间对该变量的读写操作具有可见性。它可以防止指令重排序和缓存一致性问题,但不提供原子性。 - 显式锁:Java中的
Lock
接口及其实现类(如ReentrantLock
)提供了显式的加锁和解锁操作。通过使用显式锁,可以更细粒度地控制线程之间的同步,并提供更灵活的锁定机制。 - 原子变量:Java中的
java.util.concurrent.atomic
包提供了一组原子类,如AtomicInteger
、AtomicLong
等。这些类提供了原子性的操作,可以确保特定操作在多线程环境下的原子执行。
You should avoid the temptation to think that there are “special” situations in which this rule does not apply. A program that omits needed synchronization might appear to work, passing its tests and performing well for years, but it is still broken and may fail at any moment.
在并发编程中,我们应该避免认为存在“特殊”情况不需要遵守同步规则。一个省略了必要同步的程序可能会看起来工作正常,通过测试并在很长时间内表现良好,但它仍然存在缺陷,随时可能发生故障。
即使一个程序在某个特定的环境下运行良好,没有出现问题,也不能保证它在其他环境或将来的时刻依然正常工作。并发程序的行为是不确定的,因为线程的执行顺序和交互是受到多种因素影响的,包括底层硬件和操作系统的调度策略。
因此,必须始终遵循同步规则,并根据需要正确地使用同步机制,以确保共享状态的一致性和可见性。否则,即使看起来正常的程序也可能在某个时刻出现严重的问题。
If multiple threads access the same mutable state variable without appropriate synchronization, your program is broken. There are three ways to fix it:
- Don’t share the state variable across threads;
- Make the state variable immutable; or
- Use synchronization whenever accessing the state variable.
If you haven’t considered concurrent access in your class design, some of these approaches can require significant design modifications, so fixing the problem might not be as trivial as this advice makes it sound.
如果多个线程在没有适当同步的情况下访问同一个可变状态变量,那么你的程序就是有问题的。修复这个问题有三种方式:
- 不在线程之间共享状态变量;
- 将状态变量设置为不可变;
- 在访问状态变量时使用同步机制。
如果你在类的设计中没有考虑并发访问,那么其中一些方法可能需要进行重大的设计修改,因此修复问题可能并不像这些建议听起来那么简单。
It is far easier to design a class to be thread-safe than to retrofit it for thread safety later.
设计一个线程安全的类要比事后为其添加线程安全性更容易。
这句话的意思是,在最初设计类的时候就考虑线程安全性要比在后期对其进行修改和添加线程安全性要容易得多。如果在最初的设计中就充分考虑了并发访问的情况,并采取了适当的同步措施,那么就能够确保类在多线程环境下的安全性。
在事后为类添加线程安全性可能需要对现有的代码进行较大的修改,甚至需要重新设计。这涉及到理解和分析现有代码的并发访问问题,选择适当的同步机制,并确保修改后的代码在各种并发场景下都能正确地工作。因此,为了简化开发过程并减少潜在的错误和问题,最好在最初的设计阶段就考虑并发访问,并设计出线程安全的类。
但在实际情况中,设计一个完全线程安全的类可能并不容易。这可能涉及到复杂的业务逻辑、多个状态变量之间的依赖关系以及高并发场景下的性能考虑等因素。在一些复杂的系统中,确保每个类都是完全线程安全的可能是一项巨大的挑战。
In a large program, identifying whether multiple threads might access a given variable can be complicated.
Fortunately, the same object-oriented techniques that help you write well-organized, maintainable classes—such as encapsulation and data hiding—can also help you create thread-safe classes.
The less code that has access to a particular variable, the easier it is to ensure that all of it uses the proper synchronization, and the easier it is to reason about the conditions under which a given variable might be accessed.
The Java language doesn’t force you to encapsulate state—it is perfectly allowable to store state in public fields (even public static fields) or publish a reference to an otherwise internal object—but the better encapsulated your program state,
the easier it is to make your program thread-safe and to help maintainers keep it that way.
概括一下,上面讲的是通过封装状态并限制对状态的访问,可以更好地控制多线程环境下的数据共享和访问,从而使程序更容易维护和确保线程安全性。