我有一个关于由多个线程执行的代码同步的问题:
据我所知,每个线程都有自己的堆栈,因此,非静态变量存在于每个线程的内存中的不同位置(对于X线程,存在包含所有非静态变量的X堆栈)。 那么为什么需要同步任何东西?
我的意思是,如果线程执行的代码包含一些类变量v1,那么每个线程都有自己的v1实例(不同的内存地址),没有其他线程可以“触摸”它... isn'是这样吗?
答案 0 :(得分:7)
非静态变量存在于每个线程的内存中的不同位置
这不是真的,所以
的答案如果线程执行的代码包含一些类变量v1,那么每个线程都有自己的v1实例(不同的内存地址),没有其他线程可以“触摸”它......不是吗
不是。线程可以触及由其他线程分配和修改的对象实例,并且程序员负担过重,以确保这不会影响程序的正确性。
类成员变量存在于内存每类实例的单个位置,而不是每个线程。确实,在memory barriers(认为{
的开始}
和结束synchronized
)之间,线程可能具有对象状态的缓存,但是与强制每线程存储的语言不同。 “每个线程的内存”是它的堆栈,它不包含对象成员* - 只引用对象。
最好的方法是在堆上为每个对象放置一个位置,但是可能有多个读取和写入涉及同时发生的内存位置。
如果您听说线程在堆的不同部分分配对象,我可以看到您将得出的结论。一些JVM进行了优化,它们执行thread-local allocation但不会阻止其他线程访问这些对象。
线程本地分配
如果分配器真正实现,如清单1所示,共享heapStart字段将很快成为一个重要的并发瓶颈,因为每个分配都涉及获取保护该字段的锁。为了避免这个问题,大多数JVM使用线程局部分配块,其中每个线程从堆中分配更大的内存块,并按顺序从该线程局部块中提供小的分配请求。因此,线程必须获取共享堆锁的次数大大减少,从而提高了并发性。
* - JVM优化可能允许某些对象为allocated on the stack。
答案 1 :(得分:3)
堆栈是线程安全的,而堆不是线程安全的,除非您同步代码。堆栈包含局部变量和方法参数(原始和引用),而堆包含对象。
答案 2 :(得分:2)
只保证在堆栈上分配基本类型,例如int
。对象和数组都是通常存储在堆中,除非Escape Analysis确定对象的范围是“仅限于过程的范围”。
答案 3 :(得分:2)
堆栈是(考虑调用堆栈,局部变量),但类变量存在于堆中,您必须同步对它们的访问:)
答案 4 :(得分:1)
在同一个对象实例上,如果您的方法未同步,则无法保证在不同的线程中不会执行相同的代码两次 - >浩劫!哪个是正确的值?
至少,您希望将访问变量的方法声明为synchronized。如果您想要更精细的控制,可以使用ReentrantReadWriteLock
。
声明一个方法同步在对象实例上同步,所以这是安全的。
答案 5 :(得分:1)
局部变量,基元和引用是隐式线程局部的。但是,引用的对象可以共享,当线程可以修改共享对象时,很可能需要synchronised
,Lock或其他策略来确保线程安全。
答案 6 :(得分:0)
一些关键点可以帮助澄清您的疑虑 -
对象始终在堆上分配。
跨线程(同一对象的线程)共享类级别变量
局部变量始终是线程安全的(如果不以非线程安全方式暴露给外界)
答案 7 :(得分:0)
“存在于不同位置的非静态变量”可能不正确。在Java中,您永远不会直接了解“堆栈”的任何内容。所有类变量static或instance都来自堆。但是,作为一名Java开发人员,您并不真正关心它。
你唯一不关心线程安全的时候是你的类是不可变的(在构造之后不要改变)或者你没有在线程中做任何事情。如果你的课程不属于这两个类别,你需要考虑让它们成为线程安全的。
您可以在设计中获得的不变性越多,线程问题就越容易被推理和克服。
Nrj有正确的想法。