What is multithreading? How to achieve multithreading?


Transfer from: https://blog.csdn.net/csdnnews/article/details/82321777

What is a process?

There are many programs that run separately in a computer, and each program has an independent process, and the processes exist independently of each other. For example, QQ, Kugou player, computer housekeeper and so on in the picture below.Insert picture description here

What is a thread?

Processes need to rely on threads to perform tasks. In other words, the smallest unit of execution in a process is a thread, and there is at least one thread in a process.

So what is multithreading? When it comes to multithreading, there are two concepts to talk about, serial and parallel. Only when we figure this out can we better understand multithreading.

The so-called serial is actually relative to a single thread to perform multiple tasks. Let's take the downloading file as an example: when we download multiple files, it is downloaded in a certain order in the serial Yes, that is to say, you must wait for the download of A to start downloading B, and it is impossible for them to overlap in time.
Insert picture description here
Parallel: download multiple files, open multiple threads, and download multiple files at the same time. This is in the strict sense. It happens at the same time. Parallel overlaps in time.
Insert picture description here
After understanding these two concepts, let's talk about what multithreading is. For example, we open Tencent Manager, Tencent Manager itself is a program, that is to say it is a process, there are many functions in it, we can look at the picture below, can check and kill viruses, clean up garbage, computer acceleration and many other functions .

According to the single thread, whether you want to clean up the garbage or check the virus, you must do one of the things before you can do the next thing. There is an order of execution.

If it is multi-threaded, we can actually perform other operations such as virus detection, computer acceleration, etc. when cleaning up the garbage. This happens at the same time in a strict sense, and there is no order of execution.
Insert picture description here
The above is that multiple threads are generated when a process is running.

After understanding this issue, we need to understand another issue that we have to consider when using multithreading-thread safety.

Today we will not talk about how to ensure the safety of a thread, let's talk about what is thread safety? Because I was asked in the interview before, to be honest, I didn't really understand this problem before. We seem to have only learned how to ensure a thread safety, but we don't know what the so-called safety is!

What is thread safety?

When multiple threads access a method, no matter how you call it or how these threads execute alternately, we don’t need to do any synchronization in the main program. The resulting behavior of this class is what we envision Correct behavior, then we can say that this class is thread-safe.
Since it is a thread safety issue, there is no doubt that all hidden dangers are generated in the case of multiple thread access, that is, we must ensure that our program can behave according to our expected behavior when multiple threads are accessed. To execute, let's take a look at the following code.

Integer count = 0;
public void getCount() {
       count ++;
       System.out.println(count);
 }

A very simple piece of code, let's count the number of accesses of this method, and there will be no problems when multiple threads access at the same time. I opened 3 threads and each thread looped 10 times, and got the following results:
Insert picture description here

We can see that there are two 26s here. This situation obviously shows that this method is not thread-safe at all. There are many reasons for this problem.

The most common one is that after our thread A enters the method, we get the value of count, and we just read this value out, and when the value of count has not been changed, thread B also comes in as a result, which leads to thread A and The count value received by thread B is the same.

So from this we can understand that this is indeed not a thread-safe class, because they all need to manipulate this shared variable. In fact, it is quite complicated to give a clear definition of thread safety. Let's summarize what thread safety is based on our program.

When multiple threads access a method, no matter how you call it or how these threads execute alternately, we don’t need to do any synchronization in the main program. The resulting behavior of this class is what we envision Correct behavior, then we can say that this class is thread-safe.

Having figured out what thread safety is, let's look at the two most common ways to ensure thread safety in Java. Let's look at the code first.

public void threadMethod(int j) {
int i = 1;

j = j + i;

}

Do you think this code is thread safe?

There is no doubt that it is absolutely thread-safe. Let’s analyze why it is thread-safe?

We can see that this code does not have any state, that is to say, our code does not contain any scope, nor does it refer to the scope of other classes for reference. The scope of execution and the execution result only exist. It is in the local variables of this thread and can only be accessed by the executing thread. The current thread's access will not have any impact on another thread accessing the same method.

Two threads access this method at the same time, because there is no shared data, their behavior will not affect the operations and results of other threads, so stateless objects are also thread-safe.

How about adding a status?

If we add a state to this code, add a count to record the number of hits of this method, and count+1 for each request, is this thread safe at this time?

public class ThreadDemo {

int count = 0; // record the number of hits of the method

public void threadMethod(int j) {

   count++ ;

   int i = 1;

   j = j + i;

}
}

Obviously it is not anymore. There is really no problem with single-threaded operation, but when multiple threads access this method concurrently, the problem arises. Let's analyze the count+1 operation first.

After entering this method, you must first read the value of count, then modify the value of count, and finally assign this value to count. A total of three steps are involved: "read" one> "modify" one> "assign", Since this process is step-by-step, let's look at the following picture first to see if you can see the problem:
Insert picture description here
you can find that the value of count is not the correct result. When thread A reads the value of count, but Before modification, thread B has already come in, and then thread B reads the value of count as 1. Because of this, our count value has already deviated, so such a program is placed in our code , There are many hidden dangers.

How to ensure thread safety?

Since there is a thread safety problem, then we must find a way to solve this problem, how to solve it? Let's talk about several common ways

synchronized

The synchronized keyword is used to control thread synchronization, to ensure that our threads are not executed by multiple threads at the same time in a multi-threaded environment, and to ensure the integrity of our data. The method of use is generally added to the method.

public class ThreadDemo {

int count = 0; // record the number of hits of the method

public synchronized void threadMethod(int j) {

   count++ ;

   int i = 1;

   j = j + i;

}
}

In this way, we can ensure that our threads are synchronized. At the same time, we need to pay attention to a problem that everyone usually ignores. First, synchronized locks the object in brackets, not the code. Second, for the non-static synchronized method, the lock is the object itself. It's this.

When synchronized locks an object, if other threads want to acquire the lock object, they must wait until the thread finishes executing and releases the lock object. Otherwise, it has been in a waiting state.

Note: Although adding the synchronized keyword can make our threads safer, when we use it, we must also pay attention to narrowing the scope of use of synchronized. If you use it at will, it will affect the performance of the program, and other objects want to get it. Lock, and as a result, you keep occupying the lock without using the lock, which is a bit of a waste of resources.

lock

Let me first talk about the difference between it and synchronized. Lock was introduced in Java 1.6. The introduction of Lock makes the lock operability. What does it mean? That is, when we need to manually acquire and release the lock, we can even interrupt the acquisition and the synchronization feature of timeout acquisition, but from the perspective of use, Lock is obviously not synchronized, which is convenient and quick to use. Let's first look at how it is generally used:

private Lock lock = new ReentrantLock(); // ReentrantLock是Lock的子类

private void method(Thread thread){ lock.lock(); // Acquire the lock object try { System.out.println("Thread name:"+thread.getName() + "Obtained the lock"); // Thread. sleep(2000); }catch(Exception e){ e.printStackTrace(); } finally { System.out.println("thread name:"+thread.getName() + "released the lock"); lock.unlock( ); // Release the lock object } }










To enter the method, we must first acquire the lock, and then execute our business code. The difference here is that the objects acquired by the Lock need to be released by ourselves. In order to prevent abnormalities in our code, our release lock operation is placed Finally, because the code in finally will be executed anyway.

Write a main method, open two threads to test whether our program is normal:

public static void main(String[] args) {
       LockTest lockTest = new LockTest();
   // 线程1
   Thread t1 = new Thread(new Runnable() {

       @Override
       public void run() {
           // Thread.currentThread()  返回当前线程的引用
           lockTest.method(Thread.currentThread());
       }
   }, "t1");

   // 线程2
   Thread t2 = new Thread(new Runnable() {

       @Override
       public void run() {
           lockTest.method(Thread.currentThread());
       }
   }, "t2");

   t1.start();
   t2.start();

}

As a result,
Insert picture description here
we can see that our implementation has no problems.

In fact, there are several ways to acquire locks in Lock. Let’s talk about one more, that is, tryLock() is different from Lock(). When Lock acquires the lock, if it can’t get the lock, it’s always waiting. State until the lock is obtained, but tryLock() is not like this. TryLock has a Boolean return value. If the lock is not obtained, it will return false directly and stop waiting. It will not wait forever like Lock(). Acquire the lock.

Let's look at the code:

private void method(Thread thread){
       // lock.lock(); // 获取锁对象
       if (lock.tryLock()) {
           try {
               System.out.println("线程名:"+thread.getName() + "获得了锁");
               // Thread.sleep(2000);
           }catch(Exception e){
               e.printStackTrace();
           } finally {
               System.out.println("线程名:"+thread.getName() + "释放了锁");
               lock.unlock(); // 释放锁对象
           }
       }
   }

Result: We continue to use the two threads just now to test and we can find that after thread t1 acquires the lock, thread t2 immediately comes in, and then finds that the lock is already occupied, then it is not waiting at this time.

Insert picture description here
It seems that this method doesn't feel perfect. If the first thread takes longer to obtain the lock than the second thread enters, is it also impossible to obtain the lock object?

Then, can I control it in a way so that the waiting thread can wait for 5 seconds. If the lock cannot be obtained after 5 seconds, then stop and wait. In fact, tryLock() can be set to wait. Corresponding time.

private void method(Thread thread) throws InterruptedException {
       // lock.lock(); // 获取锁对象
   // 如果2秒内获取不到锁对象,那就不再等待
   if (lock.tryLock(2,TimeUnit.SECONDS)) {
       try {
           System.out.println("线程名:"+thread.getName() + "获得了锁");

           // 这里睡眠3秒
           Thread.sleep(3000);
       }catch(Exception e){
           e.printStackTrace();
       } finally {
           System.out.println("线程名:"+thread.getName() + "释放了锁");
           lock.unlock(); // 释放锁对象
       }
   }

}

Result: Looking at the above code, we can find that although we can wait for 2 seconds when we acquire the lock object, our thread t1 takes 3 seconds to execute the task after acquiring the lock object, then thread t2 is not waiting at this time of.
Insert picture description here
Let's change this waiting time to 5 seconds, and then look at the results:

private void method(Thread thread) throws InterruptedException {
       // lock.lock(); // 获取锁对象
   // 如果5秒内获取不到锁对象,那就不再等待
   if (lock.tryLock(5,TimeUnit.SECONDS)) {
       try {
           System.out.println("线程名:"+thread.getName() + "获得了锁");
       }catch(Exception e){
           e.printStackTrace();
       } finally {
           System.out.println("线程名:"+thread.getName() + "释放了锁");
           lock.unlock(); // 释放锁对象
       }
   }

}

Result: At this time, we can see that thread t2 waits for 5 seconds to acquire the lock object and executes the task code.
Insert picture description here
The above is the way to use Lock to ensure our thread safety.

Guess you like

Origin blog.csdn.net/weixin_45682070/article/details/107413334