我们有任务要执行,
dummyMethod
。
private synchronized void dummyMethod(){
Log.d("debug", "do nothing in dummyMethod()")
}
我们多次运行该任务。
private synchronized void fooMethod() throws InterruptedException{
for (int i = 0; i < 10; i++){
dummyMethod();
}
Thread.sleep(10000)
Log.d("debug", "fooMethod() finished")
}
上面的代码同步后
dummyMethod()
立即执行10次。
但是,在下面的代码中,
dummyMethod()
仅立即被调用一次,并且只有在fooMethod()
完成后才完成并执行了9次。
private synchronized void fooMethod() throws InterruptedException{
new Thread(()->{
for (int i = 0; i < 10; i++){
dummyMethod();
}
}).start();
Thread.sleep(10000)
Log.d("debug", "fooMethod() finished")
}
在这种情况下,logcat 显示:
Long monitor contention with owner main (18077) at void ...fooMethod()(MainActivity.java:1106) waiters=0 in void ...MainActivity.dummyMethod() for 10.001s
我认为在后一种情况下它不会阻塞新线程。有人能解释一下吗?一个同步方法如何在新线程中异步多次运行另一个同步方法?
我为你的代码制作了一个完整的独立版本。
package work.basil.example.threading;
import java.time.Duration;
import java.time.Instant;
import java.util.SequencedCollection;
import java.util.concurrent.CopyOnWriteArrayList;
public class App
{
private SequencedCollection < String > log = new CopyOnWriteArrayList <> ( ); // Thread-safe `List` implementation.
public static void main ( String[] args )
{
App app = new App ( );
app.demo ( );
}
private void demo ( )
{
this.log.add ( "info | demo start | " + Instant.now ( ) );
// this.fooMethod ( );
this.fooMethodThreaded ();
this.log.add ( "info | demo end | " + Instant.now ( ) );
log.forEach ( System.out :: println );
}
private synchronized void dummyMethod ( )
{
this.log.add ( "debug | do nothing in dummyMethod() | " + Instant.now ( ) );
}
private synchronized void fooMethod ( )
{
for ( int i = 0 ; i < 10 ; i++ ) dummyMethod ( );
try { Thread.sleep ( Duration.ofSeconds ( 10 ) ); } catch ( InterruptedException e ) { throw new RuntimeException ( e ); }
this.log.add ( "debug | fooMethod() finished | " + Instant.now ( ) );
}
private synchronized void fooMethodThreaded ( )
{
new Thread ( ( ) ->
{
for ( int i = 0 ; i < 10 ; i++ ) dummyMethod ( );
} ).start ( );
try { Thread.sleep ( Duration.ofSeconds ( 10 ) ); } catch ( InterruptedException e ) { throw new RuntimeException ( e ); }
this.log.add ( "debug | fooMethodThreaded() finished | " + Instant.now ( ) );
}
}
运行时:
info | demo start | 2023-10-19T19:42:38.662271Z
debug | fooMethodThreaded() finished | 2023-10-19T19:42:48.708631Z
info | demo end | 2023-10-19T19:42:48.709687Z
debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.714712Z
debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.715071Z
debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.715137Z
debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.715205Z
debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.715264Z
debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.715321Z
检查时间戳。请注意,演示开始后整整十秒内没有任何记录。为什么要等?我们必须看看一些事实。
请注意,我们的两个方法
dummyMethod
和 fooMethodThreaded
都是同一个类 App
上的实例方法,并且都声明为 synchronized
。同一类上的同步方法将在整个实例上同步,而不是单独在每个方法上同步。因此,同一对象上的同步方法会互相阻塞;一次只能运行一个。请参阅Java 同步方法锁定对象或方法?。
添加安德鲁·S评论中指出的事实:
同一对象的所有同步块只能有一个线程同时执行它们您有一个
App
的实例在主线程中运行。在那里我们运行
fooMethodThreaded
方法。该
synchronized
方法获得了对我们唯一的
App
实例的锁定。然后我们生成一个线程,在
dummyMethod
的同一个实例上运行另一个方法
App
。此时,我们遇到了锁冲突。
fooMethodThreaded
已经锁定了名为
App
的
app
实例。因此,当另一个方法
dummyMethod
(即
synchronized
)尝试在同一个
app
对象上获取相同的锁时,它会发现该锁已被占用。所以
dummyMethod
会阻塞,等待锁释放。这只是第一个产生的线程。第二个生成的线程执行相同的操作,到达其
dummyMethod
阻塞以等待
app
锁释放的点。第三个线程也是如此。第四个、第五个、依此类推到第十个也是如此。所有十个线程都处于阻塞状态,等待锁定。所以我们就坐着等待。同时,持有名为
App
的单个
app
实例锁的线程正在休眠。该主线程休眠十秒钟。在那十秒结束时,我们的
main
实例的
app
方法退出。
app
上的锁定随后释放。随着主线程持有的原始锁释放,我们会看到级联的执行。所有十个挂起的后台线程都能够执行。