在思考Java/C#编程时,我想知道属于对象的方法在内存中是如何表示的,并且这个事实与多线程有什么关系。
- 每个对象在内存中都单独实例化一个方法吗?还是所有相同类型的对象共享一个方法实例?
- 如果是后者,执行线程如何知道使用哪个对象的属性?
- 是否可以使用反射修改C#方法的代码,针对一种类型的多个对象中的一个对象进行修改?
- 一个不使用类属性的静态方法是否总是线程安全的?
我试着想清楚这些问题,但我对它们的答案非常不确定。
你的源代码中的每个方法(在Java、C#、C++、Pascal等面向对象和过程式编程语言中)在二进制文件和内存中只有一个副本。
一个对象的多个实例具有不同的字段,但是它们都共享相同的方法代码。技术上,存在一个过程,它会接受一个隐藏的this
参数,以提供对对象执行方法的幻觉。实际上,您调用的是一个过程,并将一个结构体(一组字段)与其他参数一起传递给它。以下是一个简单的Java对象和更或多或少等效的伪C代码:
class Foo {
private int x;
int mulBy(int y) {
return x * y
}
}
Foo foo = new Foo()
foo.mulBy(3)
被翻译成了这个伪代码(封装是由编译器和运行时/虚拟机强制实现的):
struct Foo {
int x = 0;
}
int Foo_mulBy(Foo *this, int y) {
return this->x * y;
}
Foo* foo = new Foo();
Foo_mulBy(foo, 3)
你需要区分代码和它所操作的本地变量以及参数(数据)之间的差异。数据存储在调用堆栈中,每个线程都有自己的本地变量。代码可以被多个线程执行,每个线程都有自己的指令指针(指向它当前执行的方法中的位置)。另外因为this
是一个参数,所以它是线程局部的,因此每个线程可以同时操作不同的对象,即使它们运行相同的代码。我将尝试在C#的上下文中回答这个问题。基本上有三种不同类型的方法:
当您的代码被执行时,堆上基本上有两种对象。
回答您的问题:
这是一种错误的理解对象的方式。所有方法都只属于类型。可以这样看待它。方法只是一组指令。第一次调用特定的方法时,IL代码会被JIT编译成本地指令并保存在内存中。下一次调用时,地址从方法表中获取,并再次执行相同的指令。
2.如果是后者,执行线程如何知道使用哪个对象的属性? 每次在类型上调用静态方法时,都会查找相应类型对象的方法表,并找到JIT指令的地址。对于非静态方法,调用该方法的相关对象会维护在线程的本地堆栈上。基本上,您可以获得堆栈上最近的对象。那就是我们想要调用方法的对象。
3.是否可能使用反射为一个类型的多个对象中的一个修改C#方法的代码? 不,现在不可能(我为此感到感激)。原因是反射只允许代码检查。如果您弄清楚某些方法实际上意味着什么,那么您将无法更改同一程序集中的代码。