在同步方法中用新线程调用同步方法

问题描述 投票:0回答:1

我们有任务要执行,

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

我认为在后一种情况下它不会阻塞新线程。有人能解释一下吗?一个同步方法如何在新线程中异步多次运行另一个同步方法?

java java-threads android-thread android-threading
1个回答
0
投票

我为你的代码制作了一个完整的独立版本。

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
 上的锁定随后释放。

随着主线程持有的原始锁释放,我们会看到级联的执行。所有十个挂起的后台线程都能够执行。

© www.soinside.com 2019 - 2024. All rights reserved.