Java中类锁和对象锁的区别

7
人们谈论两种多线程锁定——对象锁和类锁。据我所知,只有在对象上才能进行锁定。
案例1:我们使用new或工厂方法等创建的对象。
void synchronized myMethod(Type param) {
  //will lock on the instance used to call this method
}

或者

synchronized(this) {
 //will lock on current object
}

或者

synchronized(obj1) {
 //will lock on specified obj1 object
}

案例2:关于java.lang.Class对象

这被称为类锁,可与静态字段、方法或块一起使用,因为它们属于类并在所有对象之间共享,并且与其他类属性一起使用。

static void synchronized method() {
  //will lock the Class object
}

或者

static {
  synchronized(SomeClass.class){
    int a = 2;
  }
}

我认为这也是一种对象锁,因为类被加载到JVM的方法区中,类的所有静态属性都被包装在JVM创建的一个java.lang.Class对象中。因此,在抽象层面上它是对象锁,在图中我们看到了类锁。
所以我可以推断出另一件事。正如线程锁定的对象在未被第一个线程释放之前不能被另一个线程获取一样,类锁定(java.lang.Class实例)也是以同样的方式工作的。

我想知道在同步静态方法的情况下,线程在以下两种情况下会获取哪个类的锁:

  1. 该方法是从定义它的相同类中调用的。
  2. 该方法是通过派生类和派生类名称调用的。

这是我对这个主题的理解。请添加或更正。


1
这可能会有所帮助:https://dev59.com/R3I95IYBdhLWcg3w_zas#2056256 - Eddy
2个回答

9
人们谈论两种多线程锁定 - 对象和类。
类是一个对象。在Java语言中只有一种锁定:每个对象(包括每个类)都有一个可以被synchronized块或synchronized方法锁定的互斥体。要锁定的对象在synchronized方法中是隐含的:对于实例方法,它是“this”实例,对于静态方法,它是类对象。
最常见的新手错误之一是认为两个不同的线程不能同时进入相同的synchronized块。他们可以,并且这里在StackOverflow上有很多问题和答案证明了这一点。另一个错误是认为如果一个线程在某个对象上同步,则其他线程将无法修改该对象。他们可以并且确实这样做。
同步防止两个或多个线程同时在相同的对象上同步。哪个对象是正确的对象?这完全取决于保护你的数据。例如,如果要保护的结构是链表,则访问列表的任何方法都同步到列表头。如果要保护全局数据(例如,静态变量),则要在全局对象上同步(例如,拥有变量的类对象)。重要的是,如果有由多个线程访问的读/写数据(也称为“可变数据”),则访问相同数据的每个方法都必须同步到相同的锁上。
还有另一种Java锁定,但它不在Java语言中;它在Java标准库中。通过实现java.util.concurrent.locks.Lock接口的对象可用。当然,Lock对象(像任何对象一样)也实现了第一种锁定,但除非你想给人留下无知的新手印象,否则永远不要在Lock对象上同步。
java.util.concurrent风格的锁定比使用synchronized块更强大,因为它具有显式的lock()和unlock()方法。例如,一个方法可以锁定锁,而另一个方法可以解锁它。这可能会导致难以理解的代码,除非我有非常好的理由,否则我不会这样做,但有时确实有好的理由。

如果一个线程在某个对象上同步,那么其他线程将无法修改该对象。为什么?这难道不违反了整个同步技术吗? - user6091735
@HarishAmarnath https://stackoverflow.com/questions/24068672/how-is-class-level-locking-achieved-in-java/24069242#24069242 - Solomon Slow
1
@jameslarge,是的,我明白了,如果使用对象引用和类实例同时调用静态同步方法...它将同时执行。但是想象一下将相同的对象传递给两个不同的线程...只有在任何一个线程遇到同步块之前,对象才会被同步并且无法被修改,对吧?在那之前,对象在线程之间是共享的,对吧? - user6091735
3
@HarishAmarnath,该对象永远不能被修改。synchronized (foo) 块并不会阻止其他线程修改 foo:它仅仅阻止其他线程进入 synchronized (foo) 块。 - Solomon Slow
1
@AakashVerma,Re,“...我是否仍然能够访问类的非静态属性...?”synchronized(foobar) {...}块不会阻止其他线程访问foobar的字段。它唯一防止的是,它防止其他线程同时在相同的对象上进行同步。如果您不希望您的A、B和C线程在D线程锁定foobar时访问foobar的字段,则由您来确保它们只从synchronized(foobar)块内部访问foobar - Solomon Slow
显示剩余3条评论

4
唯一的区别是,static synchronized 锁定类实例,而非静态的 synchronized 方法锁定实例本身。

人们提到了两种类型的多线程锁定。

有对象实例锁和 Lock 风格的锁。令人困惑的是,Lock 两者都具备。

对象和类

这不是真的,就像您已经发现的那样。

人们说的话并不总是正确的。通常人们会说很多废话。事实上,有一些网站专门讲解关于Java的无意义内容。:P


1
考虑两个类:class Aclass B extends A。A 有一个方法 static synchronized int add()。现在从 B 类中,如果我调用 B.add(),它会锁定 B 类对象,而如果我调用 A.add(),A 类对象将被锁定,对吗?当 B 类对象被锁定时,另一个线程无法修改其他 B 静态字段或方法等,如果它们是同步的。如果没有同步,则可以由任意数量的线程同时修改。 - xploreraj
1
@xploreraj,没有B.add(),只有A.add(),所以A.class将被锁定。你可以调用new B().add(),但它仍然会锁定A.class。当你锁定B.class时,另一个线程无法锁定B.class,但它可以访问其他任何东西,例如方法和字段。如果没有同步或锁定,多个线程执行多个写入/读取操作是没有限制的。 - Peter Lawrey
2
@xploreraj 正确。你可以有A.add() B.add(),两者都是静态和同步的。如果B.add()调用A.add()并且你调用B.add(),那么B.class和A.class都会被锁定。 - Peter Lawrey
1
@xploreraj - 关键是静态方法上没有重写/多态性。所以B.add仅仅提供了对A.add的访问。因此,唯一需要锁住的是A。 - Brett Okken
根据方法隐藏原则,我认为除非B.add()在其代码中内部和明确地调用A.add(),否则对于我们已经在B中重新实现了add(),不会对A.add()执行任何锁定操作。如果B只是调用继承的add(),那么我相信Peter关于两个类都被锁定的说法是正确的(尽管我仍然觉得只有A会被锁定)。 - Aakash Verma
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接