关于synchronized关键字如何配合锁和线程饥饿的问题

Questions about how the synchronized keyword works with locks and thread starvation

this java tutorial 中有一些代码显示了一个示例来解释 synchronized 关键字的用法。我的观点是,为什么我不应该这样写:

public class MsLunch {
    private long c1 = 0;
    private long c2 = 0;
    //private Object lock1 = new Object();
    //private Object lock2 = new Object();

    public void inc1() {
        synchronized(c1) {
            c1++;
        }
    }

    public void inc2() {
         synchronized(c2) {
             c2++;
         }
     }
}

不费心创建锁对象?另外,为什么要实例化那个锁对象呢?我不能只传递一个空引用吗?我想我在这里遗漏了一些东西。

此外,假设我在同一个 class 中有两个 public 同步方法,由多个线程访问。这两个方法真的永远不会同时执行吗?如果答案是肯定的,是否有一种内置机制可以防止一种方法饿死(与另一种方法相比,从未执行过或执行过少)?

首先你不能将原始变量传递给synchronized,它需要一个引用。其次,该教程只是一个显示受保护块的示例。它不是要保护 c1,c2,而是要保护 synchronized 块内的所有代码。

JVM 使用操作系统的调度算法。

What is the JVM Scheduling algorithm?

因此,查看线程是否饥饿不是 JVM 的责任。但是,您可以分配线程的优先级以优先执行一个线程。

Every thread has a priority. Threads with higher priority are executed in preference to threads with lower priority. Each thread may or may not also be marked as a daemon. When code running in some thread creates a new Thread object, the new thread has its priority initially set equal to the priority of the creating thread, and is a daemon thread if and only if the creating thread is a daemon.

发件人:https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html

如果您担心这种情况,则必须自己实施。就像维护一个检查饥饿线程的线程一样,随着时间的推移,它会增加等待时间比其他线程长的线程的优先级。

是的,已经同步的两个方法永远不会在同一个实例上同时执行。

正如@11thdimension 回复的那样,您不能在原始类型(例如,long)上进行同步。它必须是一个 class 对象。

因此,您可能会想要执行以下操作:

Long c1 = 0;
public void incC1() {
  synchronized(c1) {
    c1++;
  }
}

这将无法正常工作,因为 "c1++" 是 "c1 = c1 + 1" 的快捷方式,它实际上将一个新对象分配给 c1,因此,两个线程可能会在同一个块中结束同步代码。

要使锁正常工作,不应重新分配正在同步的对象。 (好吧,也许在极少数情况下你真的知道自己在做什么。)

您不能将空对象传递给 synchronized(...) 语句。 Java 在引用的对象上有效地创建信号量,并使用该信息来防止多个线程访问同一受保护资源。

您并不总是需要一个单独的锁对象,就像同步方法那样。在这种情况下,class 对象实例本身用于存储锁定信息,就好像您在方法 iteslf:

中使用了 'this'
public void incC1() {
    synchronized(this) {
      c1++;
    }
}

Why bother instantiate that lock objects? Can't I just pass a null reference?

正如其他人所提到的,您无法锁定 long c1,因为它是一个原语。 Java 锁定与对象实例关联的监视器。这就是为什么你也不能锁定 null.

thread tutorial 正在尝试展示一个好的模式,即创建 private final 锁定对象以精确控制您试图保护的互斥锁位置。在 this 或其他 public 对象上调用 synchronized 可能会导致外部调用者阻止您的方法,这可能不是您想要的。

教程对此进行了解释:

All updates of these fields must be synchronized, but there's no reason to prevent an update of c1 from being interleaved with an update of c2 — and doing so reduces concurrency by creating unnecessary blocking. Instead of using synchronized methods or otherwise using the lock associated with this, we create two objects solely to provide locks.

所以他们也试图让 c1 的更新和 c2 的更新同时发生 ("interleaved") 而不是互相阻塞,同时确保更新受到保护。

Assume that I've two public synchronized methods in the same class accessed by several thread. Is it true that the two methods will never be executed at the same time?

如果一个线程正在一个对象的 synchronized 方法中工作,如果另一个线程尝试 相同对象的相同或另一个 synchronized 方法,它将被阻塞.线程可以同时对不同个对象运行方法。

If the answer is yes, is there a built-in mechanism that prevents one method from starvation (never been executed or been executed too few times compared to the other method)?

如前所述,这是由操作系统的本机线程构造处理的。所有现代 OS' 都处理线程饥饿,如果线程具有不同的优先级,这一点尤为重要。