什么是線程安全,我們?cè)趺慈?shí)現(xiàn)它?What is Thread-Safety and How to Achieve it?
Last modified: April 17, 2020
by Alejandro Ugarte
Translater:ChangKongYi
1. Overview概述
Java supports multithreading out of the box. This means that by running bytecode concurrently in separate worker threads, the JVM is capable of improving application performance.
Java支持開箱即用(out of the box)的多線程。JVM的優(yōu)化能夠提高應(yīng)用程序性能, 這也意味著通過在單獨(dú)的工作線程中同時(shí)運(yùn)行字節(jié)碼。
Although multithreading is a powerful feature, it comes at a price. In multithreaded environments, we need to write implementations in a thread-safe way.
盡管多線程是一項(xiàng)強(qiáng)大的功能,但是是需要付出一定的代價(jià)。 在多線程環(huán)境中,我們需要有方法的去實(shí)現(xiàn)線程安全
This means that different threads can access the same resources without exposing erroneous behavior or producing unpredictable results. This programming methodology is known as “thread-safety”.
這意味著不同的線程可以訪問相同的資源,而不會(huì)暴露錯(cuò)誤的行為或產(chǎn)生不可預(yù)測(cè)的結(jié)果。 這種編程方法被稱為“線程安全”。
In this tutorial, we'll look at different approaches to achieve it.
在這個(gè)教程里,我們將在可以下方看多種實(shí)現(xiàn)線程安全的方式
2. Stateless Implementations(無狀態(tài)實(shí)現(xiàn))
In most cases, errors in multithreaded applications are the result of incorrectly (錯(cuò)誤地導(dǎo)致的結(jié)果)sharing state between several threads.
在大多數(shù)情況下,多線程應(yīng)用程序中的錯(cuò)誤是錯(cuò)誤地在多個(gè)線程之間共享狀態(tài)的結(jié)果。
Therefore, the first approach that we'll look at is to achieve thread-safety using stateless implementations.
因此,我們要研究的第一種方法是使用無狀態(tài)實(shí)現(xiàn)來實(shí)現(xiàn)線程安全。
To better understand this approach, let's consider(考慮) a simple utility class with a static method that calculates the factorial of a number:
為了更好的了解這個(gè)方式, ?讓我們考慮實(shí)現(xiàn)一個(gè)帶有靜態(tài)方法的簡(jiǎn)單實(shí)用程序類,該方法可計(jì)算數(shù)字的階乘:

The *factorial()* method is a stateless deterministic function. Given a specific input, it always produces the same output.
這個(gè)factorial方法是無狀態(tài)的確定性函數(shù)。給定特定輸入后,它始終會(huì)產(chǎn)生相同的輸出(也就是說每一個(gè)線程訪問這個(gè)factorial方法都會(huì)有一個(gè)自己特定的值 而不是在一個(gè)特定值上進(jìn)行計(jì)算)
The method neither relies on external state nor maintains state at all. Hence, it's considered to be thread-safe and can be safely called by multiple threads at the same time.
該方法既不依賴于外部狀態(tài),也不完全保持狀態(tài)。 因此,它被認(rèn)為是線程安全的,并且可以同時(shí)被多個(gè)線程安全地調(diào)用。
All threads can safely call the factorial() method and will get the expected result without interfering with each other and without altering the output that the method generates for other threads.
所有線程都可以安全地調(diào)用 factorial()方法,并且將獲得預(yù)期結(jié)果,而不會(huì)互相干擾,也不會(huì)更改該方法為其他線程生成的輸出。
Therefore, stateless implementations are the simplest way to achieve thread-safety.
因此,無狀態(tài)實(shí)現(xiàn)是實(shí)現(xiàn)線程安全最簡(jiǎn)單的方式
3. Immutable Implementations(不可變實(shí)現(xiàn))
Immutable objects are instances whose state doesn’t change after it has been initialized. For example, String is an immutable class and once instantiated its value never changes.
不可變的對(duì)象是實(shí)例,其狀態(tài)在初始化后不會(huì)改變。 例如,[String]是一個(gè)不可變的類,一旦實(shí)例化,其值就永遠(yuǎn)不會(huì)改變。
If we need to share state between different threads, we can create thread-safe classes by making them immutable.
如果我們需要在不同線程之間共享狀態(tài),則可以通過使它們的值不可變來創(chuàng)建線程安全類。
Immutability is a powerful, language-agnostic concept and it's fairly easy to achieve in Java.
不變性是一個(gè)功能強(qiáng)大,與語(yǔ)言無關(guān)的概念,在Java中相當(dāng)容易實(shí)現(xiàn)。
To put it simply, a class instance is immutable when its internal state can't be modified after it has been constructed.
簡(jiǎn)而言之,類實(shí)例在構(gòu)造后無法修改其內(nèi)部狀態(tài)時(shí)便是不可變的。
The easiest way to create an immutable class in Java is by declaring all the fields private and final and not providing setters:
在Java中創(chuàng)建不可變類的最簡(jiǎn)單方法是聲明所有字段* private 和 final *而不提供setter:

A MessageService object is effectively immutable since its state can't change after its construction. Hence, it's thread-safe.
一個(gè)MessageService對(duì)象實(shí)際上是不可變的, 自從他初始化后便不能再修改他的狀態(tài),因此他是線程安全的。
Moreover, if MessageService were actually mutable, but multiple threads only have read-only access to it, it's thread-safe as well.
而且,如果MessageService 實(shí)際上是可以改變的,但是多個(gè)線程只有讀取的權(quán)限,那么他也是線程安全的。
Thus, immutability is just another way to achieve thread-safety.
因此,不可變只是另一種實(shí)現(xiàn)線程安全的方式
4. Thread-Local Fields(線程局部字段)
In object-oriented programming (OOP), objects actually need to maintain state through fields and implement behavior through one or more methods.
在面向?qū)ο缶幊?OOP)中,對(duì)象實(shí)際上需要通過字段維護(hù)其狀態(tài)并通過一種或多種方法來實(shí)現(xiàn)行為。
If we actually need to maintain state, we can create thread-safe classes that don't share state between threads by making their fields thread-local.
如果我們真的需要去保持狀態(tài),我們可以通過將其字段設(shè)置為線程局部來創(chuàng)建不共享線程狀態(tài)的線程安全類
We can easily create classes whose fields are thread-local by simply defining private fields in Thread classes.
我們可輕松的創(chuàng)建一個(gè)定義了線程局部字段且私有的字段的線程類
通過簡(jiǎn)單地在Thread類中定義私有字段,我們可以輕松創(chuàng)建一個(gè)其字段為線程局部的類。
We could define, for instance(例如), a Thread class that stores an array of integers:
例如,我們可以定義一個(gè)Thread 類,該類存儲(chǔ)integers 的一個(gè)array :

While another one might hold an array of strings:
而另一個(gè)可能是帶有 ?string ?的 ?array ?:

在兩種實(shí)現(xiàn)上,這些類都擁有他們自己的狀態(tài),但是它的這些狀態(tài)是不會(huì)和其他線程進(jìn)行共享,因此這些類都是線程安全的。
Similarly, we can create thread-local fields by assigning(分配) ThreadLocal instances to a field.
同樣,我們可以通過將ThreadLocal實(shí)例分配給一個(gè)字段來創(chuàng)建線程本地字段。
Let's consider, for example, the following StateHolder class:
讓我們思考一下,舉個(gè)例子,下面的StateHolder 類

We can easily make it a thread-local variable as follows:
我們可以很簡(jiǎn)單將它入下面一樣變成一個(gè)線程本地的變量

Thread-local fields are pretty much like normal class fields, except that each thread that accesses them via a setter/getter gets an independently initialized copy of the field so that each thread has its own state.
線程本地字段與普通類字段非常相似,除了通過setter / getter訪問它們的每個(gè)線程都會(huì)獲得該字段的獨(dú)立初始化副本之外,以便每個(gè)線程都有自己的狀態(tài)。
5. Synchronized Collections(同步集合)
We can easily create thread-safe collections by using the set of synchronization wrappers included within the collections framework.
通過使用集合框架中包含的一組同步包裝 ?,我們可以輕易的創(chuàng)建一個(gè)線程安全的集合
We can use, for instance, one of these synchronization wrappers to create a thread-safe collection:
例如,我們可以使用以下同步包裝之一來創(chuàng)建線程安全的集合:

Let's keep in mind that synchronized collections use intrinsic locking in each method (we'll look at intrinsic locking ?later).
讓我們厘清一個(gè)概念,就是同步集合在每種方法中都有使用內(nèi)部鎖定(我們將在后面介紹內(nèi)部鎖定)
This means that the methods can be accessed by only one thread at a time, while other threads will be blocked until the method is unlocked by the first thread.
這便意味著這個(gè)方法能夠使得在同一時(shí)間里只通過一個(gè)線程訪問請(qǐng)求,然而其他的線程將會(huì)被阻塞直至這個(gè)方法被第一個(gè)線程解鎖
Thus, synchronization has a penalty in performance, due to the underlying logic of synchronized access.
因此,由于同步訪問的基本邏輯,同步的時(shí)候也會(huì)降低程序性能。
6. Concurrent Collections(并發(fā)集合)
Alternatively to synchronized collections, we can use concurrent collections to create thread-safe collections.
除了同步集合之外,我們也可以使用并發(fā)集合來創(chuàng)建一個(gè)線程安全的集合
Java provides the java.util.concurrent package, which contains several concurrent collections, such as ConcurrentHashMap:
java提供了這個(gè)XXX包,其中包括了多個(gè)并發(fā)集合,例如XXX;

Unlike their synchronized counterparts, concurrent collections achieve thread-safety by dividing their data into segments.
與同步對(duì)象不同,并發(fā)集合通過將其數(shù)據(jù)劃分為多個(gè)段來實(shí)現(xiàn)線程安全
In a ConcurrentHashMap, for instance, several threads can acquire locks on different map segments, so multiple threads can access the Map at the same time.
例如,在ConcurrentHashMap 中,多個(gè)線程可以獲取不同映射段上的鎖,所以多個(gè)線程可以同時(shí)訪問這個(gè)map
Concurrent collections are much more performant than synchronized collections, due to the inherent advantages of concurrent thread access.
由于并發(fā)線程訪問的固有優(yōu)勢(shì),并發(fā)集合比同步的集合具有更高的性能。
It's worth mentioning that synchronized and concurrent collections only make the collection itself thread-safe and not the contents.
值得一提的是,同步集合和并發(fā)集合僅使集合本身具有線程安全性,而不使其內(nèi)容變得線程安全。嗯?????
7. Atomic Objects(原子性對(duì)象)
It's also possible to achieve thread-safety using the set of atomic classes that Java provides, including AtomicInteger, AtomicLong, AtomicBoolean, and AtomicReference.
使用java提供的原子類也是可以實(shí)現(xiàn)線程安全,他們包括XXXX
Atomic classes allow us to perform atomic operations, which are thread-safe, without using synchronization. An atomic operation is executed in one single machine level operation.
原子類可以允許我們進(jìn)行沒有同步并且線程安全的原子性操作原子操作在單個(gè)機(jī)器級(jí)別的操作中執(zhí)行。
To understand the problem this solves, let's look at the following Counter class:
為了更好的了解和解決這個(gè)問題,讓我們關(guān)注下面的 Counter類

假設(shè)在競(jìng)爭(zhēng)條件下,兩個(gè)線程同時(shí)訪問\ * incrementCounter()\ *方法。
In theory, the final value of the counter field will be 2. But we just can't be sure about the result, because the threads are executing the same code block at the same time and incrementation is not atomic.
理論上,* counter *字段的最終值為2。但是我們不能確定結(jié)果,因?yàn)榫€程同時(shí)執(zhí)行在同一代碼塊時(shí),而且增量不是原子性的。
Let's create a thread-safe implementation of the Counter class by using an AtomicInteger object:
讓我們使用一個(gè)原子對(duì)象去實(shí)現(xiàn)一個(gè)線程安全的counter 類

This is thread-safe because, while incrementation, ++, takes more than one operation, *incrementAndGet* is atomic.
它是線程安全的,因?yàn)樗M(jìn)行++的時(shí)候比以往多了一些其他的操作,使得自增是原子性的操作
8. Synchronized Methods(同步方法)
While the earlier approaches are very good for collections and primitives, we will at times need greater control than that.
盡管之前的方法對(duì)于集合和原函數(shù)非常有用,但有時(shí)我們需要的控制權(quán)要遠(yuǎn)強(qiáng)于此。
So, another common approach that we can use for achieving thread-safety is implementing synchronized methods.
因此,可用于實(shí)現(xiàn)線程安全的另一種常見方法是實(shí)現(xiàn)同步方法。
Simply put, only one thread can access a synchronized method at a time while blocking access to this method from other threads. Other threads will remain blocked until the first thread finishes or the method throws an exception.
簡(jiǎn)而言之,在一時(shí)間內(nèi)只有一個(gè)線程允許訪問同步方法那么其他的想要訪問方法的線程多會(huì)被阻塞,其他的線程將會(huì)一直阻塞直到出現(xiàn)第一個(gè)線程執(zhí)行完成或者報(bào)錯(cuò)
We can create a thread-safe version of incrementCounter() in another way by making it a synchronized method:
使用同步方法作為另一種方式去實(shí)現(xiàn)一個(gè)線程安全版本的 IncrementCount()

We've created a synchronized method by prefixing the method signature(簽名) with the synchronized keyword.
我們通過在方法簽名之前添加synchornized關(guān)鍵字來創(chuàng)建一個(gè)同步方法。
Since one thread at a time can access a synchronized method, one thread will execute the incrementCounter() method, and in turn, others will do the same. No overlapping execution will occur whatsoever.
由于一個(gè)同步方法一次只允許一個(gè)線程訪問,因此一個(gè)線程將執(zhí)行* incrementCounter()*方法,而其他線程將執(zhí)行相同的方法。 任何重疊的執(zhí)行都不會(huì)發(fā)生。
Synchronized methods rely on the use of “intrinsic locks” or “monitor locks”. An intrinsic lock is an implicit internal entity associated with a particular class instance.
同步方法的實(shí)現(xiàn)依靠“內(nèi)部鎖”或“監(jiān)視器鎖” ,內(nèi)部鎖是與特定類實(shí)例關(guān)聯(lián)的隱式內(nèi)部實(shí)體。
In a multithreaded context, the term monitor is just a reference to the role that the lock performs on the associated object, as it enforces exclusive access to a set of specified methods or statements.
在多線程上下文中,術(shù)語(yǔ)monitor只是對(duì)鎖對(duì)關(guān)聯(lián)對(duì)象執(zhí)行的角色的引用,因?yàn)樗鼜?qiáng)制對(duì)一組指定的方法或語(yǔ)句進(jìn)行獨(dú)占訪問。
When a thread calls a synchronized method, it acquires the intrinsic lock.
當(dāng)一個(gè)線程調(diào)用了同步方法,它便獲得了固有鎖
After the thread finishes executing the method, it releases the lock, hence allowing other threads to acquire the lock and get access to the method.
當(dāng)這個(gè)線程成功執(zhí)行完這個(gè)方法,它便會(huì)釋放這個(gè)鎖,因此允許其他的線程獲得這個(gè)鎖去訪問這個(gè)方法
We can implement synchronization in instance methods, static methods, and statements (synchronized statements).
我們可以實(shí)現(xiàn) 實(shí)例方法同步、靜態(tài)方法同步,和同步語(yǔ)句
9. Synchronized Statements(同步語(yǔ)句)
Sometimes, synchronizing an entire method might be overkill if we just need to make a segment of the method thread-safe.
有時(shí),如果我們只需要使方法的一部分成為線程安全的,那么同步整個(gè)方法可能就顯得過分了。
To exemplify this use case, let's refactor the incrementCounter() method:
為了說明這個(gè)用例,讓我們重構(gòu)incrementCounter()方法:

The example is trivial(不重要), but it shows how to create a synchronized statement.
這個(gè)例子很簡(jiǎn)單,但是它向我們展示了如何創(chuàng)建一個(gè)同步語(yǔ)句
Assuming(假設(shè)) that the method now performs a few additional operations, which don't require synchronization, we only synchronized the relevant state-modifying section by wrapping it within a synchronized block.
假設(shè)該方法現(xiàn)在執(zhí)行一些不需要同步的附加操作,我們僅通過將相關(guān)的狀態(tài)修改部分包裝在* synchronized *塊中來進(jìn)行同步。
Unlike synchronized methods, synchronized statements must specify the object that provides the intrinsic lock, usually the this reference.
與同步方法不同,同步語(yǔ)句必須指定一個(gè)提供內(nèi)部鎖的對(duì)象,通常是引用 this 。
Synchronization is expensive, so with this option, we are able to only synchronize the relevant parts of a method.
同步非常耗性能,因此使用此選項(xiàng),我們最好只同步方法的相關(guān)部分。
9.1. Other Objects as a Lock(其他對(duì)象鎖)
We can slightly improve the thread-safe implementation of the Counter class by exploiting another object as a monitor lock, instead of this.
我們可以通過將另一個(gè)對(duì)象用作監(jiān)視器鎖而不是 this ?,來稍微改善 ?Counter ?類的線程安全實(shí)現(xiàn)。(那么這個(gè)對(duì)象和this有和不同呢)
Not only does this provide coordinated access to a shared resource in a multithreaded environment, but also it uses an external entity to enforce exclusive access to the resource:
這不僅可以在多線程環(huán)境中提供對(duì)共享資源的協(xié)調(diào)訪問,而且還使用外部實(shí)體來強(qiáng)制對(duì)資源進(jìn)行獨(dú)占訪問:

We use a plain Object instance to enforce mutual exclusion. This implementation is slightly better, as it promotes security at the lock level.
我們使用一個(gè)普通的Object實(shí)例來強(qiáng)制相互排斥。 此實(shí)現(xiàn)稍好一些,因?yàn)樗梢蕴岣哝i定級(jí)別的安全性。
When using this for intrinsic locking, an attacker could cause a deadlock by acquiring the intrinsic lock and triggering a denial of service (DoS) condition.
當(dāng)使用 this 進(jìn)行內(nèi)部鎖定時(shí), 攻擊者可能會(huì)通過獲取內(nèi)部鎖定并觸發(fā)拒絕服務(wù)(DoS)條件來導(dǎo)致死鎖。
On the contrary, when using other objects, that private entity is not accessible from the outside. This makes it harder for an attacker to acquire the lock and cause a deadlock.
相反,在使用其他對(duì)象時(shí),無法從外部訪問該私有實(shí)體。 這使攻擊者更難獲得鎖并造成死鎖。
9.2. Caveats(注意事項(xiàng))
Even though we can use any Java object as an intrinsic lock, we should avoid using Strings for locking purposes:
即使我們可以將任何Java對(duì)象用作內(nèi)部鎖定,也應(yīng)避免出于鎖定目的而使用* Strings *:

At first glance, it seems that these two classes are using two different objects as their lock.
乍一看,他好像是這個(gè)兩個(gè)類使用了兩個(gè)不同的對(duì)象作為了他們的鎖
However, because of string interning, these two “Lock” values may actually refer to the same object on the string pool. That is, the Class1 and Class2 are sharing the same lock!
但是,由于字符串interning,這兩個(gè)“ Lock”值實(shí)際上可能引用字符串池上的同一對(duì)象。 也就是說,* Class1 和 Class2 *共享著一個(gè)相同的鎖!
This, in turn, may cause some unexpected behaviors in concurrent contexts.
反過來,這可能會(huì)導(dǎo)致在并發(fā)上下文中發(fā)生某些意外行為。
In addition to Strings, we should avoid using any cacheable or reusable objects as intrinsic locks.
除了字符串之外,我們還應(yīng)避免將任何可緩存或可重用的對(duì)象用作內(nèi)部鎖。
For example, the Integer.valueOf() method caches small numbers. Therefore, calling Integer.valueOf(1) returns the same object even in different classes.
例如,Integer.valueOf()方法緩存少量數(shù)字。 因此,即使在不同的類中,調(diào)用Integer.valueOf(1)也會(huì)返回相同的對(duì)象。
10. Volatile Fields(Volatile字段)
Synchronized methods and blocks are handy for addressing (很容易解決) variable visibility problems among(之間) threads.
同步的方法和塊非常適合解決線程之間的可變可見性問題。
Even so, the values of regular class fields might be cached by the CPU. Hence, consequent updates to a particular field, even if they're synchronized, might not be visible to other threads.
即使這樣,常規(guī)類字段的值也可能會(huì)被CPU緩存。 因此,即使是同步的,對(duì)特定字段的后續(xù)更新也可能對(duì)其他線程不可見。(也就說會(huì)出現(xiàn)線程不安全 造成r/w數(shù)值錯(cuò)誤)
To prevent this situation, we can use volatile class fields:
為了避免這個(gè)情況,我們可以使用volatile類字段

With the *volatile* keyword, we instruct the JVM and the compiler to store the *counter* variable in the main memory.
使用 volatile 關(guān)鍵字,我們指示JVM和編譯器將 ?counter 變量存儲(chǔ)在主內(nèi)存中。
That way, we make sure that every time the JVM reads the value of the counter variable, it will actually read it from the main memory, instead of from the CPU cache. Likewise, every time the JVM writes to the counter variable, the value will be written to the main memory.
這樣,我們確保每次JVM讀取 counter 變量的值時(shí),實(shí)際上都會(huì)從主內(nèi)存而不是從CPU緩存讀取它。 同樣,每次JVM寫入 ?counter ?變量時(shí),該值將寫入主內(nèi)存。
Moreover, the use of a *volatile* variable ensures that all variables that are visible to a given thread will be read from the main memory as well.
此外,使用 volatile ?變量可確保也將從主內(nèi)存中 讀取給定線程可見的所有變量。
Let's consider the following example:
讓我們思考一下下面的示例:

In this case, each time the JVM writes the age volatile variable to the main memory, it will write the non-volatile name variable to the main memory as well. This assures that the latest values of both variables are stored in the main memory, so consequent updates to the variables will automatically be visible to other threads.
在這種情況下,每次JVM將 age ? volatile ?變量寫入主內(nèi)存時(shí),它也會(huì)將非 volatile name 變量也寫入主內(nèi)存。 這確保了兩個(gè)變量的最新值都存儲(chǔ)在主存儲(chǔ)器中,因此對(duì)變量的后續(xù)更新將自動(dòng)對(duì)其他線程可見。
Similarly, if a thread reads the value of a volatile variable, all the variables visible to the thread will be read from the main memory too.
同樣,如果線程讀取* volatile *變量的值,則該線程可見的所有變量也將從主內(nèi)存中讀取。
This extended guarantee that *volatile* variables provide is known as the full volatile visibility guarantee.
Volatile變量提供的這種擴(kuò)展保證稱為完全易變可見性保證
11. Reentrant Locks(重入鎖)
Java provides an improved set of Lock implementations, whose behavior is slightly more sophisticated than the intrinsic locks discussed above.
Java提供了一組改進(jìn)的Lock實(shí)現(xiàn),其行為操作比上面討論的內(nèi)部鎖稍微復(fù)雜一些。
With intrinsic locks, the lock acquisition model is rather rigid: one thread acquires the lock, then executes a method or code block, and finally releases the lock, so other threads can acquire it and access the method.
對(duì)于內(nèi)部鎖,該鎖獲取模型相當(dāng)嚴(yán)格:一個(gè)線程獲取鎖,然后執(zhí)行方法或代碼塊,最后釋放鎖,以便其他線程可以獲取它并訪問該方法。
There's no underlying mechanism that checks the queued threads and gives priority access to the longest waiting threads.
沒有一個(gè)底層機(jī)制可以檢查排隊(duì)的線程并優(yōu)先訪問等待時(shí)間最長(zhǎng)的線程。
ReentrantLock instances allow us to do exactly that, hence preventing queued threads from suffering some types of resource starvation:
而ReentrantLock實(shí)例使我們能夠做到這一點(diǎn),從而防止排隊(duì)的線程遭受某種類型的資源匱乏

The ReentrantLock constructor takes an optional fairness(公平) boolean parameter. When set to true, and multiple threads are trying to acquire a lock, the JVM will give priority to the longest waiting thread and grant access to the lock.
ReentrantLock構(gòu)造函數(shù)采用一個(gè)可選的fairness布爾參數(shù)。 如果設(shè)置為true,并且多個(gè)線程正試圖獲取鎖,則JVM將優(yōu)先考慮等待時(shí)間最長(zhǎng)的線程,并授予對(duì)該鎖的訪問權(quán)限。
12. Read/Write Locks(讀寫鎖)
Another powerful mechanism that we can use for achieving thread-safety is the use of ReadWriteLock implementations.
我們可以用來實(shí)現(xiàn)線程安全的另一種強(qiáng)大機(jī)制是使用ReadWriteLock實(shí)現(xiàn)。
A ReadWriteLock lock actually uses a pair of associated locks, one for read-only operations and other for writing operations.
ReadWriteLock 鎖實(shí)際上是使用一對(duì)關(guān)聯(lián)的鎖,一個(gè)用于只讀操作,另一個(gè)用于寫操作。
As a result, it's possible to have many threads reading a resource, as long as there's no thread writing to it. Moreover, the thread writing to the resource will prevent other threads from reading it.
結(jié)果就是,只要沒有線程寫入資源,就有可能有許多線程在讀取資源。 此外,將線程寫入資源將阻止其他線程讀取資源。
We can use a ReadWriteLock lock as follows:
正如下面 我們可以使用讀寫鎖

13. Conclusion(總結(jié))
In this article, we learned what thread-safety is in Java, and took an in-depth look at different approaches for achieving it.
在本文中,我們了解了Java中的線程安全性,并深入研究了實(shí)現(xiàn)它的不同方法。
As usual, all the code samples shown in this article are available over on GitHub