同步锁定由最短等待线程获取
Lock by synchronize is acquired by shortest waiting threads
我知道 synchronize(LOCK)
是不公平的,这意味着不能保证等待时间最长的线程会赢得锁。然而,在我下面的小实验中,锁似乎是由最短等待线程获得的...
public class Demo {
public static final Object LOCK = new Object();
public void unfairDemo(){
// Occupy the lock for 2 sec
new Thread(() -> {
synchronized (LOCK) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// Spawn 10 new threads, each with 100ms interval, to see which can win the lock
// If lock is fair then it should print the i in asc order
for (var i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
System.out.println("Added " + String.valueOf(finalI) + "th element to wait for lock");
synchronized (LOCK) {
System.out.println("I got the lock, says " + String.valueOf(finalI) + "-th thread");
}
}).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
// Keep the program alive
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行 fairDemo() 打印以下内容:
Added 0th element to wait for lock
Added 1th element to wait for lock
Added 2th element to wait for lock
Added 3th element to wait for lock
Added 4th element to wait for lock
Added 5th element to wait for lock
Added 6th element to wait for lock
Added 7th element to wait for lock
Added 8th element to wait for lock
Added 9th element to wait for lock
I got the lock, says 9-th thread
I got the lock, says 8-th thread
I got the lock, says 7-th thread
I got the lock, says 6-th thread
I got the lock, says 5-th thread
I got the lock, says 4-th thread
I got the lock, says 3-th thread
I got the lock, says 2-th thread
I got the lock, says 1-th thread
I got the lock, says 0-th thread
我以为顺序会打乱,结果怎么试都是倒序的。我这里做错了什么?
What did I do wrong here?
你做的很好。
在此特定情况下,您的过期时间显示了程序执行顺序。 Java 如果程序员方面没有特别努力,不保证任何线程执行顺序。
试试看混沌:
public class Demo {
public static final Object LOCK = new Object();
public void unfairDemo() {
createThread(0).start();
for (var i = 1; i < 5; i++) {
createThread(i).start();
}
}
private static Thread createThread(final int number) {
return new Thread(() -> {
System.out.println("Added " + number + "th element to wait for lock");
synchronized (LOCK) {
System.out.println("I got the lock, says " + number + "-th thread");
try {
Thread.sleep(number == 0 ? 2000 : 100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
});
}
public static void main(String[] args) {
new Demo().unfairDemo();
}
}
有许多来源,例如 this,已经表明不应该假设线程获取锁的顺序。但并不代表一定要打乱顺序。
它可能至少取决于 JVM 实现。例如,this document 关于 HotSpot 说:
Contended synchronization operations use advanced adaptive spinning techniques to improve throughput even for applications with significant amounts of lock contention. As a result, synchronization performance becomes so fast that it is not a significant performance issue for the vast majority of real-world programs.
...
In the normal case when there's no contention, the synchronization operation will be completed entirely in the fast-path. If, however, we need to block or wake a thread (in monitorenter or monitorexit, respectively), the fast-path code will call into the slow-path. The slow-path implementation is in native C++ code while the fast-path is emitted by the JITs.
我不是HotSpot的专家(也许其他人可以提供更权威的答案),但基于C++ code,看起来竞争线程将被推入后进先出结构,这可以解释您观察到的类似堆栈的顺序:
// * Contending threads "push" themselves onto the cxq with CAS
// and then spin/park.
...
// Cxq points to the set of Recently Arrived Threads attempting entry.
// Because we push threads onto _cxq with CAS, the RATs must take the form of
// a singly-linked LIFO.
我知道 synchronize(LOCK)
是不公平的,这意味着不能保证等待时间最长的线程会赢得锁。然而,在我下面的小实验中,锁似乎是由最短等待线程获得的...
public class Demo {
public static final Object LOCK = new Object();
public void unfairDemo(){
// Occupy the lock for 2 sec
new Thread(() -> {
synchronized (LOCK) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// Spawn 10 new threads, each with 100ms interval, to see which can win the lock
// If lock is fair then it should print the i in asc order
for (var i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
System.out.println("Added " + String.valueOf(finalI) + "th element to wait for lock");
synchronized (LOCK) {
System.out.println("I got the lock, says " + String.valueOf(finalI) + "-th thread");
}
}).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
// Keep the program alive
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行 fairDemo() 打印以下内容:
Added 0th element to wait for lock
Added 1th element to wait for lock
Added 2th element to wait for lock
Added 3th element to wait for lock
Added 4th element to wait for lock
Added 5th element to wait for lock
Added 6th element to wait for lock
Added 7th element to wait for lock
Added 8th element to wait for lock
Added 9th element to wait for lock
I got the lock, says 9-th thread
I got the lock, says 8-th thread
I got the lock, says 7-th thread
I got the lock, says 6-th thread
I got the lock, says 5-th thread
I got the lock, says 4-th thread
I got the lock, says 3-th thread
I got the lock, says 2-th thread
I got the lock, says 1-th thread
I got the lock, says 0-th thread
我以为顺序会打乱,结果怎么试都是倒序的。我这里做错了什么?
What did I do wrong here?
你做的很好。
在此特定情况下,您的过期时间显示了程序执行顺序。 Java 如果程序员方面没有特别努力,不保证任何线程执行顺序。
试试看混沌:
public class Demo {
public static final Object LOCK = new Object();
public void unfairDemo() {
createThread(0).start();
for (var i = 1; i < 5; i++) {
createThread(i).start();
}
}
private static Thread createThread(final int number) {
return new Thread(() -> {
System.out.println("Added " + number + "th element to wait for lock");
synchronized (LOCK) {
System.out.println("I got the lock, says " + number + "-th thread");
try {
Thread.sleep(number == 0 ? 2000 : 100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
});
}
public static void main(String[] args) {
new Demo().unfairDemo();
}
}
有许多来源,例如 this,已经表明不应该假设线程获取锁的顺序。但并不代表一定要打乱顺序。
它可能至少取决于 JVM 实现。例如,this document 关于 HotSpot 说:
Contended synchronization operations use advanced adaptive spinning techniques to improve throughput even for applications with significant amounts of lock contention. As a result, synchronization performance becomes so fast that it is not a significant performance issue for the vast majority of real-world programs.
...
In the normal case when there's no contention, the synchronization operation will be completed entirely in the fast-path. If, however, we need to block or wake a thread (in monitorenter or monitorexit, respectively), the fast-path code will call into the slow-path. The slow-path implementation is in native C++ code while the fast-path is emitted by the JITs.
我不是HotSpot的专家(也许其他人可以提供更权威的答案),但基于C++ code,看起来竞争线程将被推入后进先出结构,这可以解释您观察到的类似堆栈的顺序:
// * Contending threads "push" themselves onto the cxq with CAS // and then spin/park. ... // Cxq points to the set of Recently Arrived Threads attempting entry. // Because we push threads onto _cxq with CAS, the RATs must take the form of // a singly-linked LIFO.