我们有一个任务要执行,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
我认为在后一种情况下它不会阻塞新线程。有人能解释一下吗?一个同步方法如何在新线程中多次异步运行另一个同步方法?
从我的评论来看——
来自这篇 Baeldung文章: 同一对象的所有同步块只能有一个线程同时执行它们
因此当前线程已经处于同步方法中,并且新线程无法立即执行,
dummyMethod()
因为它是同一个对象。对原始代码进行轻微修改,显示使用新实例确实允许新线程立即运行
dummyMethod()
:[警告:我不是 Java 并发方面的专家。]
我为你的代码制作了一个完整的独立版本。
此代码用作
CopyOnWriteArrayList
线程安全日志。运行时:
检查时间戳。请注意,演示开始后整整十秒内没有任何记录。为什么要等?我们必须看一些事实。
请注意,我们的两个方法
dummyMethod
&fooMethodThreaded
都是同一个类 上的实例方法,App
并且都声明为synchronized
。同一类上的同步方法将在整个实例上同步,而不是单独在每个方法上同步。因此,同一对象上的同步方法会互相阻塞;一次只能运行一个。看到Java同步方法锁定对象或方法吗?。添加Andrew S 的评论中指出的事实:
App
您有一个在主线程中运行的实例。我们在那里运行该fooMethodThreaded
方法。该synchronized
方法锁定了我们唯一的App
实例。dummyMethod
然后我们生成一个线程来在 的同一个实例上运行另一个方法App
。此时,我们遇到了锁冲突。已经
fooMethodThreaded
锁定了我们的App
named实例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 限制,也就是说,如果它执行一些阻塞,请使用虚拟线程。
通过这种方法,我们可以在开始和结束时看到演示的开始和结束。我们看到任务的每个所需执行都会在日志中记录。
完整的应用程序代码: