同步访问共享的可变数据外文翻译资料

 2022-08-09 04:08

Synchronize access to shared mutable data

The synchronized keyword ensures that only a single thread can execute a method or block at one time. Many programmers think of synchronization solely as a means of mutual exclusion, to prevent an object from being seen in an inconsistent state by one thread while itrsquo;s being modified by another. In this view, an object is created in a consistent state and locked by the methods that access it. These methods observe the state and optionally cause a state transition, transforming the object from one consistent state to another.Proper use of synchronization guarantees that no method will ever observe the object in an inconsistent state.

This view is correct, but itrsquo;s only half the story. Without synchronization, one threadrsquo;s changes might not be visible to other threads. Not only does synchronization prevent threads from observing an object in an inconsistent state, but it ensures that each thread entering a synchronized method or block sees the effects of all previous modifications that were guarded by the same lock.

The language specification guarantees that reading or writing a variable is atomic unless the variable is of type long or double [JLS, 17.4, 17.7]. In other words, reading a variable other than a long or double is guaranteed to return a value that was stored into that variable by some thread, even if multiple threads modify the variable concurrently and without synchronization.

You may hear it said that to improve performance, you should dispense with synchronization when reading or writing atomic data. This advice is dangerously wrong. While the language specification guarantees that a thread will not see an arbitrary value when reading a field, it does not guarantee that a value written by one thread will be visible to another. Synchronization is required for reliablecommunication between threads as well as for mutual exclusion. This is due to a part of the language specification known as the memory model, which specifies when and how changes made by one thread become visible to others [JLS, 17.4; Goetz06, 16].

The consequences of failing to synchronize access to shared mutable data can be dire even if the data is atomically readable and writable. Consider the task of stopping one thread from another. The libraries provide the Thread.stop method, but this method was deprecated long ago because it is inherently unsafe —its use can result in data corruption. Do not use Thread.stop. A recommended way to stop one thread from another is to have the first thread poll a boolean field that is initially false but can be set to true by the second thread to indicate that the first thread is to stop itself. Because reading and writing a boolean field is atomic, some programmers dispense with synchronization when accessing the field:

// Broken! - How long would you expect this program to run?

public class StopThread {

private static boolean stopRequested;

public static void main(String[] args)

throws InterruptedException {

Thread backgroundThread = new Thread(() -gt; {

int i = 0;

while (!stopRequested)

i ;

});

backgroundThread.start();

TimeUnit.SECONDS.sleep(1);

stopRequested = true;

}

}

You might expect this program to run for about a second, after which the main thread sets stopRequested to true, causing the background threadrsquo;s loop to terminate. On my machine, however, the program never terminates: the background thread loops forever!

The problem is that in the absence of synchronization, there is no guarantee as to when, if ever, the background thread will see the change in the value of stopRequested made by the main thread. In the absence of synchronization, itrsquo;s quite acceptable for the virtual machine to transform this code:

while (!stopRequested)

i ;into this code:

if (!stopRequested)

while (true)

i ;

This optimization is known as hoisting, and it is precisely what the OpenJDK Server VM does. The result is a liveness failure: the program fails to make progress. One way to fix the problem is to synchronize access to the stopRequested field. This program terminates in about one second, as

expected:

// Properly synchronized cooperative thread termination

public class StopThread {

private static boolean stopRequested;

private static synchronized void requestStop() {

stopRequested = true;

}

private static synchronized boolean stopRequested() {

return stopRequested;

}

public static void main(String[] args)

throws InterruptedException {

Thread backgroundThread = new Thread(() -gt; {

int i = 0;

while (!stopRequested())

i ;

});

backgroundThread.start();

TimeUnit.SECONDS.sleep(1);

requestStop();

}

}

Note that both the write method (requestStop) and the read method

(stop-Requested) are synchronized. It is not sufficient to synchronize only the write method! Synchronization is not guaranteed to work unless both read and write operations are synchronized. Occasionally a program that synchronizes only writes (or reads) may appear to work on some machines, but in this case, appearances are deceiving.

The actions of the synchronized methods in StopThread would be atomic

even without synchronization. In other words, the synchronization on thesemethods is used solely for its communication effects, not for mutual exclusion. While the cost of synchronizin

剩余内容已隐藏,支付完成后下载完整资料


同步访问共享的可变数据

synchronized关键字可确保只有一个线程可以一次执行一个方法或块。 许多程序员仅将同步视为互斥的一种手段,以防止对象在一个线程被另一个线程修改时被视为处于不一致状态。 在此视图中,将以一致的状态创建对象,并通过访问该对象的方法将其锁定。 这些方法观察状态并有选择地引起状态转换,从而将对象从一种一致状态转换为另一种状态。正确使用同步保证了没有方法可以观察到处于不一致状态的对象。

这种观点是正确的,但这只是故事的一半。 没有同步,一个线程的更改可能对其他线程不可见。 同步不仅可以防止线程以不一致的状态观察对象,而且还可以确保进入同步方法或块的每个线程都能看到由同一锁保护的所有先前修改的效果。

语言规范保证读取或写入变量是原子的,除非该变量的类型为long或double [JLS,17.4,17.7]。换句话说,即使多个线程同时并没有同步地修改变量,保证读取非长整型或双精度型的变量也可以确保返回某个线程存储在该变量中的值。

您可能会听到它说要提高性能,在读取或写入原子数据时应放弃同步。这个建议是错误的。虽然语言规范保证了线程在读取字段时不会看到任意值,但并不能保证由一个线程写入的值对另一线程可见。同步是线程之间可靠通信以及相互排斥所必需的。这是由于语言规范的一部分称为内存模型,该规范指定了一个线程何时以及如何对其他线程可见更改[JLS,17.4; Goetz06,16]。

即使共享数据是原子可读和可写的,也无法对共享的可变数据进行同步访问,因此后果不堪设想。考虑从另一个线程停止一个线程的任务。这些库提供了Thread.stop方法,但是很久以前不赞成使用此方法,因为它本质上是不安全的-使用该方法可能会导致数据损坏。不要使用Thread.stop。建议从一个线程停止另一个线程的方法是让第一个线程轮询一个布尔值字段,该字段最初为false,但可以由第二个线程设置为true,以指示第一个线程将自行停止。由于读取和写入布尔型字段是原子的,因此某些程序员在访问该字段时无需进行同步:

public class StopThread {

private static boolean stopRequested;

public static void main(String[] args)

throws InterruptedException {

Thread backgroundThread = new Thread(() -gt; {

int i = 0;

while (!stopRequested)

i ;

});

backgroundThread.start();

TimeUnit.SECONDS.sleep(1);

stopRequested = true;

}

}

您可能希望该程序运行大约一秒钟,然后主线程将stopRequested设置为true,

从而导致后台线程的循环终止。 但是,在我的机器上,该程序永不终止:后台

线程永远循环!

问题在于,在没有同步的情况下,无法保证后台线程何时会看到主线程对stopRequested值的更改。 在没有同步的情况下,虚拟机转换此代码是完全可以接受的:

while (!stopRequested)

i ;into this code:

if (!stopRequested)

while (true)

i ;

这种优化称为提升,这正是OpenJDK Server VM所做的。 结果是活动失败:程序无法取得进展。 解决该问题的一种方法是同步对stopRequested字段的访问。 该程序像预期的那样终止于大约一秒钟:

public class StopThread {

private static boolean stopRequested;

private static synchronized void requestStop() {

stopRequested = true;

}

private static synchronized boolean stopRequested() {

return stopRequested;

}

public static void main(String[] args)

throws InterruptedException {

Thread backgroundThread = new Thread(() -gt; {

int i = 0;

while (!stopRequested())

i ;

});

backgroundThread.start();

TimeUnit.SECONDS.sleep(1);

requestStop();

}

}

请注意,write方法(requestStop)和read方法(stop-Requested)已同步。仅同步写入方法是不够的!除非读取和写入操作都已同步,否则不能保证同步。有时,仅同步写入(或读取)的程序可能在某些计算机上可以运行,但是在这种情况下,外观很欺骗。

StopThread中同步方法的操作将是原子的,即使没有同步。换句话说,在这些方法上的同步仅用于其通信效果,而不用于相互排斥。尽管在循环的每次迭代中进行同步的代价很小,但是有一个正确的替代方法,它不那么冗长,并且其性能可能会更好。如果将stopRequested声明为volatile,则可以省略第二版StopThread中的锁定。尽管volatile修饰符不执行互斥,但它保证读取该字段的任何线程都将看到最新写入的值:

public class StopThread {

private static volatile boolean stopRequested;

public static void main(String[] args)

throws InterruptedException {

Thread backgroundThread = new Thread(() -gt; {

int i = 0;

while (!stopRequested)

i ;

});

backgroundThread.start();

TimeUnit.SECONDS.sleep(1);

stopRequested = true;

}

}

使用挥发物时一定要小心。 考虑以下方法,应该生成序列号:

// Broken - requires synchronization!

private static volatile int nextSerialNumber = 0;

public static int generateSerialNumber() {

return nextSerialNumber ;

}

该方法的目的是确保每次调用都返回唯一值(只要不超过232个调用)。方法的状态由单个原子可访问字段nextSerialNumber组成,并且该字段的所有可能值都是合法的。因此,无需同步即可保护其不变性。但是,如果没有同步,该方法将无法正常工作。

问题在于增量运算符( )不是原子的。它对nextSerialNumber字段执行两项操作:首先读取该值,然后nit写回一个新值,该值等于旧值加一个。如果第二个线程在一个线程读取旧值并写回一个新值之间读取该字段,则第二个线程将看到与第一个相同的值,并返回相同的序列号。这是安全故障:程序计算出错误的结果。

修复generateSerialNumber的一种方法是添加已同步的声明的修饰符。这样可以确保不会交错执行多个调用,并且确保该方法的每次调用都可以看到以前所有调用的效果。完成此操作后,您可以并且应该从nextSerialNumber中删除volatile修饰符。为了使该方法更安全,请使用long而不是int,如果nextSerialNumber将要包装,则抛出异常。

更好的是,遵循第59条中的建议,并使用AtomicLong类,该类是java.util.concurrent.atomic的一部分。该套餐提供用于对单个变量进行无锁,线程安全编程的原语。尽管volatile仅提供同步的通信效果,但此包还提供了原子性。这正是我们想要的generateSerialNumber,它的性能可能优于同步版:

// Lock-free synchronization with java.util.concurrent.atomic

private static final AtomicLong nextSerialNum = new AtomicLong();

public static long generateSerialNumber() {

return nextSerialNum.getAndIncrement();

}

避免本项目中讨论的问题的最佳方法是不共享可变数据。共享不可变数据(第17项),或者根本不共享。换句话说,将可变数据限制在单个线程中。如果你采用此策略,重要的是要记录在文档中,以便在程序演变时保持该策略。深入了解您使用的框架和库也很重要,因为它们可能会引入您不知道的线程。

一个线程可以修改一个数据对象一段时间,然后与其他线程共享它,仅同步共享对象引用的行为,这是可以接受的。然后,其他线程可以读取对象而无需进一步同步,只要不再次对其进行修改即可。据说这些对象实际上是不可变的[Goetz06,3.5.4]。将这样的对象引用从一个线程转移到另一个线程称为安全发布[Goetz06,3.5.3]。有很多方法可以安全地发布对象引用:您可以将其存储在静态字段中,作为类初始化的一部分;您可以将其存储在易失性字段,最终字段或通过常规锁定访问的字段中;或者您可以将其放入并发集合中(项目81)。

总而言之,当多个线程共享可变数据时,读取或写入数据的每个线程必须执行同步。在没有同步的情况下,不能保证一个线程的更改对另一线程可见。无法同步共享的可变数据的惩罚是活动性和安全性失败。这些故障是最难调试的。它们可能是间歇性的,并且与时间有关,并且程序行为可能在一个VM与另一个VM之间发生根本性的变化。如果只需要线程间通信,而不需要互斥,则volatile修饰符是可接受的同步形式,但是正确使用它可能会比较棘手。

避免过度同步

项目78警告同步不充分的危险。该项目涉及相反的问题。根据情况,过度的同步可能会导致性能降低,死锁甚至不确定的行为。

为避免人员伤亡和安全故障,切勿在同步方法或块内将控制权交给客户。换句话说,在同步区域内,请勿调用旨在被覆盖的方法,或由客户端以功能对象的形式提供的方法(项目24)。从具有同步区域的类的角度来看,此类方法是异类的。该类不知道该方法的用途,也无法对其进行控制。根据异类方法的作用,从同步区域调用它可能会导致异常,死锁或数据损坏。

为了更具体,请考虑以下类,该类实现了一个可观察的集合包装器。当元素添加到集合中时,它允许客户端订阅通知。这是观察者模式[Gamma95]。为简便起见,当从集合中删除元素时,该类不提供通知,但是提供它们将是一件简单的事情。此类在第18项(第90页)的可重用ForwardingSet之上实现:

// Broken - invokes alien method from synchronized block!

public class ObservableSetlt;Egt; extends ForwardingSetlt;Egt; {

public ObservableSet(Setlt;Egt; set) { super(set); }private final Listlt;SetObserverlt;Egt;gt; observers

= new ArrayListlt;gt;();

public void addObserver(SetObserverlt;Egt; observer) {

synchronized(observers) {

observers.add(observer);

}

}

public boolean removeObserver(SetObserverlt;Egt; observer) {

synchronized(observers) {

return observers.remove(observer);

}

}

private void notifyElementAdded(E element) {

synchronized(observers) {

for (SetObserverlt;Egt; observer : observers)

observer.added(this, element);

剩余内容已隐藏,支付完成后下载完整资料


资料编号:[238966],资料为PDF文档或Word文档,PDF文档可免费转换为Word

原文和译文剩余内容已隐藏,您需要先支付 30元 才能查看原文和译文全部内容!立即支付

以上是毕业论文外文翻译,课题毕业论文、任务书、文献综述、开题报告、程序设计、图纸设计等资料可联系客服协助查找。