Java中原子操作是线程安全的论调经常被提到。根据定义,原子操作是不会被打断地的操作,因此被认为是线程安全的。实际上有一些原子操作不一定是线程安全的。
这个问题出现的原因是尽量减少在代码中同步要害字。同步会损害性能,虽然这个损失因JVM不同而不同。另外,在现代的 JVM中,同步的性能正在逐步提高。尽管如此,使用同步仍然是有性能代价的,并且程序员永远会尽力提高他们的代码的效率,因此这个问题就延续了下来。
在java中,32位或者更少位数的赋值是原子的。在一个32位的硬件平台上,除了double和long型的其它原始类型通常都是使用 32位进行表示,而double和long通常使用64位表示。另外,对象引用使用本机指针实现,通常也是32位的。对这些32位的类型的操作是原子的。
这些原始类型通常使用32位或者64位表示,这又引入了另一个小小的神话:原始类型的大小是由语言保证的。这是不对的。java语言保证的是原始类型的表数范围而非JVM中的存储大小。因此,int型总是有相同的表数范围。在一个JVM上可能使用32位实现,而在另一个JVM上可能是64位的。在此再次强调:在所有平台上被保证的是表数范围,32位以及更小的值的操作是原子的。
那么,原子操作在什么情况下不是线程安全的?主要的一点是他们也许确实是线程安全的,但是这没有被保证!java线程答应线程在自己的内存区保存变量的副本。答应线程使用本地的私有拷贝进行工作而非每次都使用主存的值是为了提高性能。考虑下面的类:
class RealTimeClock
{
private int clkID;
public int clockID()
{
return clkID;
}
public void setClockID(int id)
{
clkID = id;
}
//...
}
现在考虑RealTimeClock的一个实例以及两个线程同时调用setClockID和clockID,并发生以下的事件序列:
T1 调用setClockID(5)
T1将5放入自己的私有工作内存
T2调用setClockID(10)
T2将10 放入自己的私有工作内存
T1调用clockID,它返回5
5是从T1的私有工作内存返回的
对 clockI的调用应该返回10,因为这是被T2设置的,然而返回的是5,因为读写操作是对私有工作内存的而非主存。赋值操作当然是原子的,但是因为 JVM答应这种行为,因此线程安全不是一定的,同时,JVM的这种行为也不是被保证的。
两个线程拥有自己的私有拷贝而不和主存一致。假如这种行为出现,那么私有本机变量和主存一致必须在以下两个条件下:
1、变量使用volatile声明
2、被访问的变量处于同步方法或者同步块中
假如变量被声明为volatile,在每次访问时都会和主存一致。这个一致性是由java 语言保证的,并且是原子的,即使是64位的值。(注重很多JVM没有正确的实现volatile要害字。你可以在www.javasoft.com找到更多的信息。)另外,假如变量在同步方法或者同步块中被访问,当在方法或者块的入口处获得锁以及方法或者块退出时释放锁是变量被同步。
使用任何一种方法都可以保证ClockID返回10,也就是正确的值。变量访问的频度不同则你的选择的性能不同。假如你更新很多变量,那么使用volatile可能比使用同步更慢。记住,假如变量被声明为volatile,那么在每次访问时都会和主存一致。与此对照,使用同步时,变量只在获得锁和释放锁的时候和主存一致。但是同步使得代码有较少的并发性。
假如你更新很多变量并且不想有每次访问都和主存进行同步的损失或者你因为其它的原因想排除并发性时可以考虑使用同步。
分享到:
相关推荐
原子操作是指不可分割的操作,一旦开始执行,就不会被其他线程中断,直到该操作完成。在多线程环境中,不使用原子操作可能会导致数据竞争(Data Race)和其他并发问题。 在上面的例子中,`CountClass`类有一个静态...
线程安全是指当多个线程访问同一块代码时,如果每个线程都能得到预期的结果,且不产生数据不一致或同步问题,那么这块代码就被称为线程安全的。Java中的线程安全问题通常表现为竞态条件、死锁、活锁和饥饿现象。 ##...
多线程编程安全退出需要设计一定的算法来让线程自己主动地退出,而不能简单地使用线程退出函数来强制某个线程的退出。通过使用读写锁和计数器,可以实现多线程的安全退出,避免程序异常的出现。
需要注意的是,完全由线程安全的类组成的程序并不一定线程安全,因为线程安全问题也可能出现在类之间的交互上。在设计程序时,考虑到线程安全通常可以提高系统的稳定性和性能。理想的线程数量通常是CPU核心数加一,...
一个原子操作是不会被中断的,即使在多线程环境下也是如此。Java提供了`java.util.concurrent.atomic`包,其中包含了一系列原子类,如`AtomicInteger`,它们提供了原子性的增减操作,解决了上述例子中线程a和线程b...
“线程池”(Thread Pool)是一种线程管理机制,预先创建一定数量的线程,当有新的任务需要执行时,从线程池中获取空闲线程而不是每次都创建新的线程。线程池可以有效减少线程创建和销毁的开销,提高系统效率。在...
在编程领域,线程安全是多线程编程中的一个关键概念,确保多个线程同时访问共享资源时不会引发数据不一致或错误状态。本篇文章将深入探讨“线程安全代码块”及其相关知识点,主要围绕Java语言中的`synchronized`...
线程安全是指一个函数在多线程环境下执行时,无论多少个线程同时调用它,都能得到预期的结果,不会出现数据不一致或其他错误。线程安全分为三种类型:无影响(thread-neutral)、线程安全(thread-safe)和递归安全...
- 使用原子操作或不可变对象也是保证线程安全的有效手段。 8. **线程池**: - 为避免频繁创建和销毁线程带来的开销,线程池预先创建一定数量的线程,任务到来时直接从池中获取线程执行,完成后归还到池中。 - ...
3. **使用AtomicInteger**:使用`AtomicInteger`来替代`int`类型的`balance`字段,利用原子操作保证线程安全。 #### 六、结论 Java线程安全是多线程编程的基础,它涉及到内存模型、同步机制等多个方面。正确理解和...
7. **线程安全**:线程安全的代码是指在多线程环境下正确运行,不会因竞态条件或数据不一致问题而导致错误。程序员需要关注原子操作、 volatile关键字、线程局部存储等机制,确保代码在并发环境下的正确性。 8. **...
在Java编程中,原子操作(Atomic Operation)是并发编程中的一种重要概念,它指的是在多线程环境下,某个操作能够不被其他线程中断,保证其完整性。这在并发编程中至关重要,因为线程间的相互干扰可能导致数据不一致...
Java提供了多种机制来保证线程安全,包括同步关键字(如`synchronized`)、原子变量类(如`AtomicInteger`)等。 #### 八、线程属性 - **线程体**:所有的操作都发生在线程体中,在Java中线程体是从`Thread`类继承...
这里我们可以使用std::ofstream来处理文件操作,确保每次写入都是原子性的,以防止数据不完整或交错。 ```cpp class Logger { private: void flushToLogFile() { std::ofstream logFile("log.txt", std::ios_base...
Atomic类提供了一组原子操作,可以在多线程环境下保证操作的原子性。 线程池是管理线程的一种机制,它可以帮助我们更好地控制线程的创建和销毁,提高系统资源的利用率。Java的ExecutorService和ThreadPoolExecutor...
实现线程安全的方式包括原子操作、不可变对象和无状态对象等。 5. **线程优先级与调度**:操作系统根据线程的优先级和调度算法决定哪个线程先执行。理解这些原理有助于优化多线程程序的性能。 6. **线程池**:为了...
- **3.3.5 线程安全和RunLoop对象**:确保对RunLoop的操作是线程安全的。 ##### 3.4 配置RUNLOOP的源 根据需要配置不同的RunLoop源: - **3.4.1 定义自定义输入源**:创建自定义输入源以扩展RunLoop的功能。 - **...
10. **原子变量类**:AtomicInteger、AtomicLong等原子变量类提供了原子操作,能够在不使用synchronized的情况下实现线程安全。 以上仅是《Java线程实战手册》可能涵盖的部分内容,每一部分都需要深入学习和实践...
值得注意的是,尽管原子操作可以提供性能优势,它们并不保证操作本身的性能就一定好。原子操作的性能取决于它们的具体实现和使用的场景。理解你正在做的操作始终是至关重要的。原子库为我们提供了一套工具,但最终...