AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 77325919
Accepted
Hong
Hong
Asked: 2023-10-20 01:27:59 +0800 CST2023-10-20 01:27:59 +0800 CST 2023-10-20 01:27:59 +0800 CST

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

  • 772

我们有一个任务要执行,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")
}

通过上面的代码,synchronizeddummyMethod()立即执行了 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
  • 2 2 个回答
  • 70 Views

2 个回答

  • Voted
  1. Best Answer
    Andrew S
    2023-10-20T04:09:23+08:002023-10-20T04:09:23+08:00

    从我的评论来看——

    来自这篇 Baeldung文章: 同一对象的所有同步块只能有一个线程同时执行它们

    因此当前线程已经处于同步方法中,并且新线程无法立即执行,dummyMethod()因为它是同一个对象。

    对原始代码进行轻微修改,显示使用新实例确实允许新线程立即运行dummyMethod():

    package sandbox;
    
    public class SyncTest {
    
        public static void main(String[] args) throws InterruptedException {
            var test = new SyncTest();
            test.fooMethod();
        }
    
        private synchronized void dummyMethod() {
            System.out.println("do nothing in dummyMethod()");
        }
    
        private synchronized void fooMethod() throws InterruptedException {
            var t = new SyncTest(); // new instance will have its own monitor
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    t.dummyMethod();
                }
            }).start();
    
            Thread.sleep(10000);
            System.out.println("fooMethod() finished");
        }
    }
    
    • 3
  2. Basil Bourque
    2023-10-20T04:04:23+08:002023-10-20T04:04:23+08:00

    [警告:我不是 Java 并发方面的专家。]

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

    此代码用作CopyOnWriteArrayList线程安全日志。

    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同步方法锁定对象或方法吗?。

    添加Andrew S 的评论中指出的事实:

    同一对象的所有同步块只能有一个线程同时执行它们

    App您有一个在主线程中运行的实例。我们在那里运行该fooMethodThreaded方法。该synchronized方法锁定了我们唯一的App实例。dummyMethod然后我们生成一个线程来在 的同一个实例上运行另一个方法App。

    此时,我们遇到了锁冲突。已经fooMethodThreaded锁定了我们的Appnamed实例app。因此,当另一个方法 dummyMethod试图synchronized获取同一个app对象上的相同锁时,它会发现该锁已被占用。所以dummyMethod阻塞,等待锁释放。

    所以我们坐着等待。App同时,持有单个实例锁的原始线程app正在休眠。该主线程休眠十秒钟。在那十秒结束时,fooMethodThreaded我们app实例的方法退出。app当同步方法退出时,锁就会释放。

    释放原始锁后,当后台线程执行其工作时,我们会看到一系列日志条目,每次连续调用dummyMethod都能够访问该锁。app

    请注意,我们在日志中没有看到“在 dummyMethod() 中不执行任何操作”的 10 个条目。这是因为我们的方法在后台线程完成之前main就已经转移到了它。log.forEach

    解决方案

    如果您确实想在后台线程上执行此任务十次,我会建议类似下面所示的代码。

    如果您想将十个任务的运行限制为一次仅十个,请使用synchronized该方法以外的其他内容。我们已经了解到,在同一个类上使用两个方法synchronized会互相阻塞。双方法阻止不是我们的目标。所以使用另一种方法来锁定。一种方法是在方法内的单独对象上进行同步,而不是在方法上进行同步。请参阅问题的答案,其他同步方法。我更喜欢使用明确的方法,如同一问题的答案中所示。另见另一个问题,方法级别的synchronized可以用Lock代替吗?。

    在现代Java中,我们很少Thread直接寻址。相反,请使用 Java 5 中添加的 Executors 框架。

    利用ExecutorService最近成为的事实AutoCloseable。所以我们可以在 try-with-resources 语法中使用它。

    如果您的任务不受 CPU 限制,也就是说,如果它执行一些阻塞,请使用虚拟线程。

    private void fooMethodWithExecutorService ( )
    {
        this.onlyTenTasksAtATimeLock.lock ( );
        try
        {
            try (
                    ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor ( ) ;
            )
            {
                int countTaskExecutions = 10;
                for ( int index = 0 ; index < countTaskExecutions ; index++ )
                {
                    executorService.submit ( this :: dummyMethod );
                }
            }
        }
        finally
        {
            this.onlyTenTasksAtATimeLock.unlock ( );
        }
    }
    

    通过这种方法,我们可以在开始和结束时看到演示的开始和结束。我们看到任务的每个所需执行都会在日志中记录。

    info | demo start | 2023-10-19T21:19:42.158048Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.165986Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166079Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166119Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166210Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166234Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166268Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166294Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166318Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166334Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166370Z
    info | demo end | 2023-10-19T21:19:42.166437Z
    

    完整的应用程序代码:

    package work.basil.example.threading;
    
    import java.time.Duration;
    import java.time.Instant;
    import java.util.SequencedCollection;
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class App
    {
        private final SequencedCollection < String > log = new CopyOnWriteArrayList <> ( );  // Thread-safe `List` implementation.
        private final Lock onlyTenTasksAtATimeLock = new ReentrantLock ( );
    
        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.fooMethodWithExecutorService ( );
            this.log.add ( "info | demo end | " + Instant.now ( ) );
            log.forEach ( System.out :: println );
        }
    
        private void fooMethodWithExecutorService ( )
        {
            this.onlyTenTasksAtATimeLock.lock ( );
            try
            {
                try (
                        ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor ( ) ;
                )
                {
                    int countTaskExecutions = 10;
                    for ( int index = 0 ; index < countTaskExecutions ; index++ )
                    {
                        executorService.submit ( this :: dummyMethod );
                    }
                }
            }
            finally
            {
                this.onlyTenTasksAtATimeLock.unlock ( );
            }
        }
    
        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 ( ) );
        }
    }
    
    • 2

相关问题

  • Lock Condition.notify 抛出 java.lang.IllegalMonitorStateException

  • 多对一微服务响应未出现在邮递员中

  • 自定义 SpringBoot Bean 验证

  • Java 套接字是 FIFO 的吗?

  • 为什么不可能/不鼓励在服务器端定义请求超时?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    使用 <font color="#xxx"> 突出显示 html 中的代码

    • 2 个回答
  • Marko Smith

    为什么在传递 {} 时重载解析更喜欢 std::nullptr_t 而不是类?

    • 1 个回答
  • Marko Smith

    您可以使用花括号初始化列表作为(默认)模板参数吗?

    • 2 个回答
  • Marko Smith

    为什么列表推导式在内部创建一个函数?

    • 1 个回答
  • Marko Smith

    我正在尝试仅使用海龟随机和数学模块来制作吃豆人游戏

    • 1 个回答
  • Marko Smith

    java.lang.NoSuchMethodError: 'void org.openqa.selenium.remote.http.ClientConfig.<init>(java.net.URI, java.time.Duration, java.time.Duratio

    • 3 个回答
  • Marko Smith

    为什么 'char -> int' 是提升,而 'char -> Short' 是转换(但不是提升)?

    • 4 个回答
  • Marko Smith

    为什么库中不调用全局变量的构造函数?

    • 1 个回答
  • Marko Smith

    std::common_reference_with 在元组上的行为不一致。哪个是对的?

    • 1 个回答
  • Marko Smith

    C++17 中 std::byte 只能按位运算?

    • 1 个回答
  • Martin Hope
    fbrereto 为什么在传递 {} 时重载解析更喜欢 std::nullptr_t 而不是类? 2023-12-21 00:31:04 +0800 CST
  • Martin Hope
    比尔盖子 您可以使用花括号初始化列表作为(默认)模板参数吗? 2023-12-17 10:02:06 +0800 CST
  • Martin Hope
    Amir reza Riahi 为什么列表推导式在内部创建一个函数? 2023-11-16 20:53:19 +0800 CST
  • Martin Hope
    Michael A fmt 格式 %H:%M:%S 不带小数 2023-11-11 01:13:05 +0800 CST
  • Martin Hope
    God I Hate Python C++20 的 std::views::filter 未正确过滤视图 2023-08-27 18:40:35 +0800 CST
  • Martin Hope
    LiDa Cute 为什么 'char -> int' 是提升,而 'char -> Short' 是转换(但不是提升)? 2023-08-24 20:46:59 +0800 CST
  • Martin Hope
    jabaa 为什么库中不调用全局变量的构造函数? 2023-08-18 07:15:20 +0800 CST
  • Martin Hope
    Panagiotis Syskakis std::common_reference_with 在元组上的行为不一致。哪个是对的? 2023-08-17 21:24:06 +0800 CST
  • Martin Hope
    Alex Guteniev 为什么编译器在这里错过矢量化? 2023-08-17 18:58:07 +0800 CST
  • Martin Hope
    wimalopaan C++17 中 std::byte 只能按位运算? 2023-08-17 17:13:58 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve