What do you got?
- Further understand the problems that can arise in multi-threaded scenarios;
- Learn to properly handle communication and synchronization in concurrent operations.
Now, learn more about threads
In daily development, threads are often used as an important means to improve program efficiency. In this CoorChice post, CoorChice introduces the basic workings of threads. Links:
From Thread to Handle
In this article, CoorChice will further introduce the knowledge related to threads from the perspective of multi-threading. First, we need to know some basics.
Main memory and working memory
- Main memory: can be thought of as heap memory in the memory model. It stores all the shared variables of the process. We know that there can be multiple threads in a process, including the main thread. The shared variables in main memory are visible to all threads. ++
- Working memory: For efficiency, each thread has a private working memory. A shared variable in the main memory needs to be copied to the thread’s private memory, and the thread then performs operations on the variable in its own working memory. ++ When the value is changed, it is updated to main memory before the thread exits. ++
To learn more about Java memory, take a look at these articles from CoorChice: 1. You need to know something about memory reclamation
2. Never heard of memory jitter
3. Memory leaks that can’t be ignored
Shared and unshared variables
-
Shared variable: If a variable has copies in the working memory of multiple threads, it is considered a shared variable. In fact, class member variables, static variables are shared variables. As mentioned above, shared variables are visible to all threads in the process. The concurrency problems we often encounter are often caused by it.
-
Unshared variables: Are private variables in the thread. These variables are not visible to other threads. They are reclaimed when the thread exits. The value of a non-shared variable requires communication to be passed to other threads, which will be discussed later.
other
-
Atomic operation: an indivisible, continuous operation. For example, the Read operation will be mentioned later.
-
Visibility: Changes made by one thread to the value of a shared variable are visible to other threads in real time.
Problems caused by shared variables
At this point, boots know that shared variables are visible to all threads in the process. And when a thread needs to use it, it needs to make a copy of it into its working memory and then manipulate the copy object in its working memory. The following diagram shows the process of manipulating shared variables in a thread.
The figure shows a thread reading/writing to a shared variable. As you can see, the ++ and the + are made up of two atomic operations. Note that the “CoorChice” sentence means that the normal reading or writing of a variable is not an atomic operation, but rather a two-step operation.
read
- Read: Reads variable values from main memory into the thread’s working memory.
- Load: Assigns the read value to the new copy variable.
write
- Store: Transfers the value of a copy of a shared variable in the thread’s working memory to the main memory.
- Write: Assigns the value after store to a shared variable in main memory.
You see, both reading and writing require two steps, so it is likely to be interrupted. The following code, for example, may have different results each time it is executed.
int goods = 0;
@Test
public void testThread() {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
while(goods ! = 10) { goods++; System.out.println( Thread.currentThread().getName() +" -> Goods = "+ goods); }},"Thread - "+ i).start(); }}Copy the code
First run result:
Thread - 0 -> Goods = 1
Thread - 0 -> Goods = 3
Thread - 0 -> Goods = 4
Thread - 1 -> Goods = 2
Thread - 2 -> Goods = 6
Thread - 2 -> Goods = 8
Thread - 2 -> Goods = 9
Thread - 2 -> Goods = 10
Thread - 0 -> Goods = 5
Thread - 1 -> Goods = 7
Copy the code
Result of the second run
Thread - 0 -> Goods = 1
Thread - 1 -> Goods = 2
Thread - 0 -> Goods = 3
Thread - 0 -> Goods = 5
Thread - 0 -> Goods = 6
Thread - 2 -> Goods = 7
Thread - 2 -> Goods = 9
Thread - 1 -> Goods = 4
Thread - 2 -> Goods = 10
Thread - 0 -> Goods = 8
Copy the code
This example gets this result because when one thread executes, another thread inserts execution. Key insertion places may include:
1. In the process of reading/writing the shared variable goods.
2. The goods++ operation contains +1 and assignment operations.
In fact, if you operate on non-primitive variables, your program may be vulnerable to a crash. If we want the program to run efficiently and correctly, we need to solve the problem of communication (information or data transfer) and synchronization (ordered execution) in multi-threaded scenarios.
Multithreaded communication and synchronization
Currently, we have roughly two sets of models for solving multithreading problems.
- Model based on memory sharing.
Threads communicate through shared memory, that is, information in shared memory is publicly visible, but needs to be displayed for synchronization. Otherwise, the confusion of the above example would occur. It is not hard to see that the shared memory model is characterized by implicit communication and display synchronization. Java’s chosen concurrency solution is based on shared memory. This is why you often need to use Synchronized or Lock in Java for synchronization.
- A model based on messaging.
Synchronization is achieved by sending/receiving messages between threads. Since messages are always sent and received sequentially (sent first, received later), this model is characterized by implicit synchronization and display communication, that is, communication needs to be transmitted with additional information at the time the message is sent. Handlers in Android are based on the messaging model. The Handler mechanism CoorChice is described in detail in this article: Thread to Handle
Let’s take a look at synchronization in Java.
Thread synchronization means
synchronized
The synchronized keyword is familiar, and is often added to methods or blocks of code for use in synchronization:
public synchronized void testThread() {... }Copy the code
Or sync code blocks like this:
public void testThread() { Object object = new Object(); Synchronized (this){// Synchronized (this){ } synchronized (object){// Specify the object lock... } synchronized (object.class){// synchronized (object.class) Note that this means that only one object instance of the class can access the code block at a time. . }}Copy the code
When synchronizing, you need to keep in mind that you need to synchronize where you really need to synchronize, not in large chunks, which can effectively reduce the efficiency of the program! Remember: keep synchronization granularity as small as possible!
Lock
Compared to sycnhronized, Lock is a manual synchronization. In Java, a ReentrantLock is implemented to help us achieve synchronization. It is also relatively simple to use. We only need to lock the front part of the code block that needs to be synchronized and release the lock at the end. Let’s look at an example.
int goods = 0;
public void testThread() {
Lock lock = new ReentrantLock();
for (int i = 0; i < 3; i++) {
new Thread(() -> {
lock.lock();
while (goods < 10) {
goods++;
System.out.println(
Thread.currentThread().getName() +
" -> Goods = " + goods);
}
lock.unlock();
}, "Thread - "+ i).start(); }}Copy the code
Same example above, let’s see what happens this time.
Thread - 0 -> Goods = 1
Thread - 0 -> Goods = 2
Thread - 0 -> Goods = 3
Thread - 0 -> Goods = 4
Thread - 0 -> Goods = 5
Thread - 0 -> Goods = 6
Thread - 0 -> Goods = 7
Thread - 0 -> Goods = 8
Thread - 0 -> Goods = 9
Thread - 0 -> Goods = 10
Copy the code
Synchronizing with a Lock requires that the Lock be released in a timely manner where an exception occurs, otherwise other threads waiting to acquire the Lock will be blocked! In addition, if you use mlock.trylock () to obtain the lock, you can determine whether the lock was successfully obtained based on the return value.
Does final have synchronization?
The answer is yes, but it only guarantees synchronization in certain cases. What do they look like? That’s for immutable objects. An immutable object (an object whose member variables are made up of primitive types or final modifications, or other immutable objects) means that we cannot modify it after it is safely published, so it is the same for all threads that can see it.
For mutable objects (non-immutable objects, such as ordinary lists, maps, etc.), even if final is used, you still need to synchronize the display in concurrent scenarios. Because the contents of mutable objects can be modified. If you look at an example, the boots might understand it more clearly.
final AlterableObj obj = new AlterableObj();
@Test
public void testThread_2() {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (obj.var < 100) {
obj.var++;
System.out.println(
Thread.currentThread().getName() +
" -> AlterableObj.var = " + obj.var)
}
}, "Thread - " + i).start();
}
}
class AlterableObj{
public int var = 0;
}
Copy the code
The results are quite long, so I’ve taken only a few that illustrate the point:
. Thread - 2 -> AlterableObj.var = 42 Thread - 2 -> AlterableObj.var = 43 Thread - 2 -> AlterableObj.var = 44 Thread - 1 -> AlterableObj.var = 40 Thread - 4 -> AlterableObj.var = 46 Thread - 4 -> AlterableObj.var = 48 Thread - 4 -> AlterableObj.var = 49 Thread - 4 -> AlterableObj.var = 50 Thread - 4 -> AlterableObj.var = 51 Thread - 3 -> AlterableObj.var = 39 ...Copy the code
Look, it’s gone wrong! So FIANL does not guarantee synchronization of mutable objects.
Does volatile have synchronization?
The primary purpose of volatile is to ensure the visibility of the variable being modified. This means that the read/write operations of volatile variables are analogous to atomic ++, that is, read and load, and stroe and write processes become continuous and uninterruptible. Thus, volatile is synchronization in a sense, but it is very small and often not sufficient for our needs.
In addition, volatile provides some degree of order. The JVM rearranges instructions for the program at compile time, but this does not affect the result. If a variable is volatile, the program instructions that occurred before its read/write operation are not rearranged after its read/write operation. Such as:
volatile int a = 0;
int b = 1;
int c = 2;
int a = 3;
int d = 4;
int e = 5;
Copy the code
In the code above, int a = 3 acts as a barrier such that int b = 1 and int c = 2 must occur before int D = 4 and int e = 5.
They come with synchronization properties
Under the java.util.concurrent package, Java provides thread-safe versions of many common objects, such as the AtomicXXX family, ConcurrentXXX family, CopyOnWriteXXX family, etc. In general, you can safely use them without the hassle of multithreaded scenarios!
Use multiple threads!
At this point, boots should be able to use multithreading reasonably to improve program efficiency.
In Android, because the main thread (UI thread) is responsible for drawing the interface, it should not block! If you accidentally mix in time-consuming operations in the main thread, the consequences can be dire. Light results in interface lag, heavy results in ANR! For more information, check out CoorChice’s article on the startup flow of an Activity
.
For complex calculations, data reads/writes, network access and other time-consuming operations, we should put them into threads. These days devices often have multiple cpus, such as 8-core devices that can run at least 8 threads in parallel! Not doing some concurrent operations is a total coup de grace. We just need to carefully handle the communication and synchronization between threads. Of course, it’s not as easy as it sounds, and it takes a little more time to think and try. Java also provides a number of efficient and simplified classes to help with concurrent programming, such as CoorChice’s “Learn something about ExecutorService.”
conclusion
- CoorChice needs motivation to take the time to write and share, so please give CoorChice a thumbs up
- CoorChice is always creating new dry products from time to time, and to get on board, just head over to the homepage and click follow. Start then ~
This article mainly introduces some points that need to be paid attention to in multi-threaded scenarios. According to these characteristics, the communication and synchronization between threads should be carefully handled when conducting concurrent operations.
Refer to the link
- Java Volatile Keyword:http://tutorials.jenkov.com/java-concurrency/volatile.html;
- Java memory model (a) : http://www.cloudchou.com/softdesign/post-631.html;
- Java Multithreading – Visibility Issues: https://mritd.me/2016/03/20/Java-%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E5%8F%AF%E8%A7%81%E6%80%A7%E9%97%AE%E9%A2%98/;
- Java multi-thread dry series – (4) the volatile keyword | nuggets technical essay: https://juejin.cn/post/6844903477177483272;
See here children’s shoes quickly reward yourself with a spicy stick!