英文:
Variable synchronization in multiple Java Threads
问题
<br>
我的问题是关于如何使用块synchronized,我的类FileAdapter有一个方法write,它接收HTTP连接结果的InputStream,我用它来下载文件,每下载一千字节并写入磁盘,它都会调用DownloadReport类实例的downloaded方法,以传递已下载内容。
<br>
<br>
在另一个线程中,该线程将输出显示给用户,它调用DownloadReport类的updateProgress方法,该方法负责更新在终端上显示给用户的进度条。
<br>
<br>
问题在于,如果FileAdapter类尝试在输出线程尝试更新进度条时刚好更新已下载字节数,那么问题就会出现,因为两个方法都会编辑变量intermediateDownloaded的值,该变量仅作为辅助变量,用于保存自上次更新以来已下载的字节数,以计算下载速度。
<br>
<br>
如果我在downloaded和updateProgress方法内使用***"synchronized (this)"块,它将锁定整个DownloadReport类,输出线程只能在FileAdapter***类更新已下载字节数后才能更新进度条?
<br>
<br>
FileAdapter:
public void write(InputStream content, DownloadReport downloadReport) throws IOException {
FileOutputStream output = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int read;
while ((read = content.read(buffer)) != -1) {
output.write(buffer, 0, read);
downloadReport.downloaded(read);
}
}
DownloadReport:
public void downloaded(int bytes) {
intermediateDownloaded += bytes;
downloaded += bytes;
}
public void updateProgress() {
long now = System.currentTimeMillis();
double delta = UnitHelper.sizeRound(((now - lastTimeUpdate) / 1000.0), 2);
if (delta >= 1) {
unitAdapter.convertSpeed(intermediateDownloaded, delta);
intermediateDownloaded = 0;
lastTimeUpdate = now;
}
progressBar.updateProgress(unitAdapter.finalSize,
unitAdapter.recalculate(downloaded), unitAdapter.unity);
}
英文:
<br>
My question is about how to use the block synchronized, my class FileAdapter has a method write that receives the InputStream of the result of an HTTP connection I’m using to download a file, for every kilobyte downloaded and written to disk, it calls the downloaded method of the DownloadReport class instance that it received, for pass on what has already been downloaded.
<br>
<br>
In another Thread, which is printing the output to the user, it calls the updateProgress method, also of the DownloadReport class, this method is responsible for updating a progress bar that is displayed to the user on the terminal.
<br>
<br>
The problem will be if the FileAdapter class tries to update the amount of bytes downloaded just when the output Thread tries to update the progress bar, as both methods edit the value of the variable intermediateDownloaded, which works only as an auxiliary variable, to hold the amount of bytes downloaded since the last update, to calculate the download speed.
<br>
<br>
If I use the "synchronized (this)" block, within the downloaded and updateProgress methods, it will block the entire class DownloadReport, and the output Thread will only be able to update the progress bar after the FileAdapter class updates the number of downloaded bytes?
<br>
<br>
FileAdapter:
public void write(InputStream content, DownloadReport downloadReport) throws IOException {
FileOutputStream output = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int read;
while ((read = content.read(buffer)) != -1) {
output.write(buffer, 0, read);
downloadReport.downloaded(read);
}
}
DownloadReport:
public void downloaded(int bytes) {
intermediateDownloaded += bytes;
downloaded += bytes;
}
public void updateProgress() {
long now = System.currentTimeMillis();
double delta = UnitHelper.sizeRound(((now - lastTimeUpdate) / 1000.0), 2);
if (delta >= 1) {
unitAdapter.convertSpeed(intermediateDownloaded, delta);
intermediateDownloaded = 0;
lastTimeUpdate = now;
}
progressBar.updateProgress(unitAdapter.finalSize,
unitAdapter.recalculate(downloaded), unitAdapter.unity);
}
答案1
得分: 1
首先,这种类型的同步在过去的十年或更长时间里已经不再被广泛采用,因为:
- 它非常难以正确实现
- 它在性能方面存在问题(由于等待)
- 它无法进行测试。
使用这种方法可能会导致代码中始终存在错误,可能会引发竞态条件,而您将不会知道,也无法编写单元测试来确保您的代码不受竞态条件的影响。现代在线程之间进行通信的方法是通过使用消息队列传递不可变消息。
如果您坚持以这种方式进行操作,synchronize( this )
是一个不好的想法,因为持有对您对象的引用的任何人也可以执行 synchronized( yourObject )
,然后您就会发生死锁。对于 synchronized
方法也是如此,因为编译器在幕后使用 synchronized( this )
来实现它们。所以,也不要那样做。总是声明一个私有对象用作锁。
此外,正如您似乎已经理解的那样,同步锁需要尽量少时间处于活动状态,以避免阻塞其他可能需要获取该锁的线程。因此,它需要封装尽可能少的指令。
在您的代码案例中,如果我理解正确,您需要执行以下操作:
private final Object lock = new Object();
public void downloaded(int bytes) {
synchronized (lock) {
downloaded += bytes;
}
}
然后,在稍后的代码中,无论何时访问 downloaded
,您还必须在与 lock
的同步中执行,您需要找到一些其他方法来基于仅 downloaded
计算出您的奇怪的 intermediateDownloaded
变量,以便它不需要参与同步。
或者,您可以使用 java.util.concurrent.atomic.AtomicLong downloaded
替换 long downloaded
,这将允许您在不需要同步的情况下更高效地读取和更新它。
英文:
First of all, this type of synchronization has fallen out of grace in the recent decade or more, because:
- it is extremely hard to do it right
- it suffers performance-wise (due to waiting)
- it is untestable.
Using this approach there may always be bugs in your code, possibly leading to race conditions, and you would not know, and there is no unit test you can write to ensure that your code is free from race conditions. The modern approach to communicating between threads is by passing immutable messages using message queues.
Now, if you insist on doing it this way, synchronize( this )
is a bad idea, because whoever holds a reference to your object can also do synchronized( yourObject )
and then you have a deadlock. The same holds true for synchronized
methods, because the compiler implements them using synchronized( this )
under the hood. So, do not do that either. Always declare a private object to use as a lock.
Also, as you seem to already understand, a synchronization lock needs to be active for as little as possible, in order to avoid blocking other threads that might also need to acquire that lock. So, it needs to wrap as few instructions as possible.
In the case of your code, if I understand it correctly, you need to do the following:
private final Object lock = new Object();
public void downloaded( int bytes )
{
synchronized( lock )
{
downloaded += bytes;
}
}
then further down whenever you access downloaded
you must also do it in synchronization with lock
, and you need to find some other way of calculating that weird intermediateDownloaded
variable of yours based on downloaded
only, so that it does not need to take part in the synchronization.
Alternatively, you can replace long downloaded
with java.util.concurrent.atomic.AtomicLong downloaded
which will allow you to read it and update it much more performantly than if you use synchronization.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论