You may not really understand thread safety, don’t lie to yourself, let’s see how to achieve thread safety

What is a process?

There are many programs that run independently 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.

You may not really understand thread safety, don’t lie to yourself, let’s see how to achieve thread safety

 

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 by understanding this 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 they cannot overlap in time.

You may not really understand thread safety, don’t lie to yourself, let’s see how to achieve thread safety

 

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.

You may not really understand thread safety, don’t lie to yourself, let’s see how to achieve thread safety

 

After understanding these two concepts, let's talk about what multithreading is. For example, we open Tencent Manager. Tencent Manager itself is a program, which means that it is a process with many functions. We can look at the picture below, which can check and kill viruses, clean up garbage, and accelerate computers. .

According to single-threaded terms, whether you want to clean up garbage or check for viruses, you must do one of the things before you can do the next. 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 the strict sense, and there is no order of execution.

You may not really understand thread safety, don’t lie to yourself, let’s see how to achieve thread safety

 

The above is that multiple threads are generated when a process is running.

After understanding this issue, we need to understand another issue that must be considered when using multithreading-thread safety.

Today we are not talking 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 do not 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 when multiple threads access, our program can still behave as we expected 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 below, 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:

You may not really understand thread safety, don’t lie to yourself, let’s see how to achieve thread safety

 

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, and when the value of count has not been changed, as a result, thread B also comes in, then thread A and The count value received by thread B is the same.

Then 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 do not 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;
}

1234567

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 has no state, that is to say, our code does not contain any scope, nor does it refer to the scope of other classes. 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. Therefore, 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; // 记录方法的命中次数

   public void threadMethod(int j) {

       count++ ;

       int i = 1;

       j = j + i;
   }
}

1234567891011121314

Obviously it is not anymore. There is really no problem with single thread running, but when multiple threads access this method concurrently, the problem arises. Let's analyze the operation of count+1 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:

You may not really understand thread safety, don’t lie to yourself, let’s see how to achieve thread safety

 

It can be found that the value of count is not the correct result. When thread A reads the value of count, but it has not been modified, thread B has entered, and then thread B reads the value of count as 1. Because of this, our count value has already deviated, so there are many hidden dangers in putting such a program in our code.

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 the 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 multithreaded 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; // 记录方法的命中次数

   public synchronized void threadMethod(int j) {

       count++ ;

       int i = 1;

       j = j + i;
   }
}

1234567891011121314

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.

After the 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, we should also pay attention to reducing the scope of use of synchronized when we use it. If we 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 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 the timeout acquisition, but from the perspective of use, Lock is obviously not synchronized and convenient 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(); // 获取锁对象
       try {
           System.out.println("线程名:"+thread.getName() + "获得了锁");
           // Thread.sleep(2000);
       }catch(Exception e){
           e.printStackTrace();
       } finally {
           System.out.println("线程名:"+thread.getName() + "释放了锁");
           lock.unlock(); // 释放锁对象
       }
   }

123456789101112131415

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 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();
   }

1234567891011121314151617181920212223242526

result

You may not really understand thread safety, don’t lie to yourself, let’s see how to achieve thread safety

 

It can be seen that our execution 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 returns false directly and stops 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(); // 释放锁对象
           }
       }
   }

123456789101112131415

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

You may not really understand thread safety, don’t lie to yourself, let’s see how to achieve thread safety

 

It seems that this method doesn't feel perfect. If the time for the first thread to get the lock is longer than the time for the second thread to come in, is it also impossible to get 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(); // 释放锁对象
           }
       }
   }

12345678910111213141516171819

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.

You may not really understand thread safety, don’t lie to yourself, let’s see how to achieve thread safety

 

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(); // 释放锁对象
           }
       }
   }

12345678910111213141516

Result: At this time, we can see that thread t2 waited 5 seconds to obtain the lock object and executed the task code.

You may not really understand thread safety, don’t lie to yourself, let’s see how to achieve thread safety

 

The above is the way to use Lock to ensure our thread safety.

Guess you like

Origin blog.csdn.net/weixin_48612224/article/details/109232304