Java concurrent programming summary 1: Concurrency, java thread (including: status, blocked wakeup, etc.)

The knowledge of concurrent programming that has been mastered is summarized here to form a framework and system for easy reference in the future.

One, the concept of concurrency

1.1. What is concurrency;

  • Through the form of concurrent programming, the computing power of the multi-core CPU can be maximized, and the performance can be improved;
  • In the face of complex business models, parallel programs are more suitable for business needs than serial programs, and concurrent programming is more suitable for this business split.

1.2. Processes and threads;

  • Process: the basic unit for allocating and managing resources
  • Thread: The smallest unit of CPU scheduling that must exist depending on the process.
  • For the Java language, Java programs run on the JVM, and each JVM is actually a process. All resource allocation is based on the JVM process. In this JVM process, many threads can be created, JVM resources are shared among multiple threads, and multiple threads can execute concurrently.

1.3. Java thread scheduling strategy: preemptive type (java adopted), cooperative type;

1.4. Advantages and disadvantages of multithreaded programming (context switching, deadlock (thread safety), etc.);

  • Context switching (because of this, concurrency is not necessarily faster than serial): When concurrent execution of cumulative operations is below tens of millions, concurrency will be slower than serial, and when more than tens of millions of concurrent operations are faster than serial, this is precisely because Threads have creation and context switching overhead.

Two, java thread

2.1, java thread:

  • Every program has at least one thread-the thread that runs in the main method of the main class as a Java virtual machine (JVM) startup parameter.
  • A java program is executed from the main() method , and then executed according to the established code logic. It seems that no other threads participate, but in fact the java program is born a multi-threaded program , including: (1) Distribute processing and send to JVM Signal thread; (2) the thread that calls the finalize method of the object; (3) the thread that clears the Reference; (4) the main thread, the entry point of the user program.

2.2. Three ways to create java threads:

  • Inherit the Thread class;
  • Implement the Runnable interface;
  • Implement the Callable interface;

See the specific demo:

 public class CreateThreadDemo {
 
     public static void main(String[] args) {
         //1.继承Thread
         Thread thread = new Thread() {
             @Override
             public void run() {
                 System.out.println("继承Thread");
                 super.run();
             }
         };
         thread.start();


         //2.实现runable接口
         Thread thread1 = new Thread(new Runnable() {
             @Override
             public void run() {
                 System.out.println("实现runable接口");
             }
         });
         thread1.start();


         //3.实现callable接口
         ExecutorService service = Executors.newSingleThreadExecutor();
         Future<String> future = service.submit(new Callable() {
             @Override
             public String call() throws Exception {
                 return "通过实现Callable接口";
             }
         });
         try {
             String result = future.get();
             System.out.println(result);
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
     }
 
 }
  • Since java cannot inherit multiple, but can implement multiple interfaces, so when creating threads, try to consider the form of implementing interfaces as much as possible;
  • Implement the Callable interface and submit to the ExecutorService to return the result of asynchronous execution;
  • In addition, you can usually use FutureTask (Callable<V> callable) to wrap the callable and then submit FeatureTask to ExecutorsService. See below for details:
FutureTask<String> futureTask = new FutureTask<>(new Callable() {
     @Override
     public String call() throws Exception {
         return "通过实现Callable接口";
     }
 });
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
  • Implementation relationship of FutureTask interface:

2.3. The priority of Java threads:

  • In fact, the scheduling strategy of the thread scheduler of the JVM determines the operation mechanism of the upper multi-thread. The thread scheduler of the JVM uses a preemptive scheduling method , and the execution time of each thread is allocated and managed by it. It will allocate thread execution time according to the thread priority recommendations. The higher the priority, the longer the time it may take to get the CPU.
  • Java divides the thread priority into 10 levels, and the default priority is used when the thread is created if it is not explicitly declared . JVM will allocate the probability of execution time according to the priority of each thread. Java defines three constants:
public final static int MIN_PRIORITY = 1; //最小优先级。
public final static int NORM_PRIORITY = 5; //默认优先级
public final static int MAX_PRIORITY = 10; //最大优先级
  • In addition, since the implementation of JVM is based on the host operating system, there must be a certain mapping relationship between the Java priority value and the native thread priority of various operating systems , so that the priority of all operating systems can be encapsulated to provide a unified priority Level semantics. For example, a priority value of 1-10 may be mapped with a priority value of -20-19 in Linux, while a Windows system has 9 priority values ​​to be mapped.

2.4. The state and transition of java thread:

This picture is derived from the book "The Art of Concurrent Programming in JAVA" . The thread will switch between different states. The Java thread thread conversion diagram is shown in the figure above.

  • After the thread is created, call the start() method to start running;
  • When the wait(), join(), LockSupport.lock() methods are called, the thread will enter the WAITING state;
  • And the same wait(long timeout), sleep(long), join(long), LockSupport.parkNanos(), LockSupport.parkUtil() adds the function of timeout waiting, that is, the thread will enter the TIMED_WAITING state after calling these methods . After the timeout waiting time is reached, the thread will switch to the Runable state, and in the WAITING and TIMED _WAITING states, the thread can be converted to the Runable state through the Object.notify(), Object.notifyAll() methods;
  • When a thread competes for resources, that is, while waiting to acquire a lock, the thread enters the BLOCKED state. When the thread acquires the lock, the thread enters the Runable state. After the thread runs, the thread enters the TERMINATED state, and the state transition can be said to be the life cycle of the thread. Another thing to note is:
  • The difference between synchronized and lock : When the thread enters the synchronized method or synchronized code block, the thread switches to the BLOCKED state, while the thread switches to the WAITING or TIMED_WAITING state when using the lock under java.util.concurrent.locks to lock. , Because lock will call the LockSupport method.

Description of six states:

2.5, the final interrupt mechanism of java thread:

Sometimes we want to cancel the task during the execution of the task and stop the thread. At this time, the interrupt operation will be involved. It is not easy to stop a thread safely, quickly, and reliably in Java, and Java does not provide any reliable method to terminate the execution of a thread.

There are preemptive and cooperative modes in the thread scheduling strategy, and the interrupt mechanism is similar, corresponding to the preemptive mode and the cooperative mode. The preemptive interrupt is to directly initiate an interrupt instruction to a thread, and at this time, no matter what state the thread is in, it must be interrupted immediately. The core of cooperative interrupt is to maintain an interrupt flag. When the flag is marked as interrupt, the thread executes the interrupt operation at the appropriate time node.

  • Preemptive interrupt: thread.stop(), now JDK has been deprecated;
  • Cooperative interruption : the current method;

After a long period of development, Java finally chose to use a cooperative interrupt mechanism to achieve interruption;

The principle of cooperative interrupt is very simple. Its core uses an interrupt variable as the identifier , that is, to identify the interrupt flag of a thread. The thread marked with the interrupt bit will throw an exception at the appropriate time. We catch the exception and do it. Deal with it accordingly.

jdk provides us with three functions to interrupt threads:

  • interrupt() : Thread instance method, which must be called by other threads after obtaining the instance of the called thread. In fact, the only change has changed internally calling thread's interrupt status .
  • Thread.interrupted() : Thread class method, which must be called in the current execution thread, this method returns the internal interrupted status of the current thread, and then clears the interrupted status (set to false).
  • isInterrupted() : Thread instance method: used to check the interruption status of the specified thread. When the thread is interrupted, it will return true; otherwise, it will return false. In other words: when an InterruptedException is thrown, the interrupt flag bit will be cleared, which means that the call to isInterrupted will return false.

  • The interrupt mechanism can be used as a way of simple interaction between threads:

The simple interaction between threads can also be realized by interruption. While (sleepThread.isInterrupted()) means that sleepThread will be continuously monitored in Main. Once the interrupt flag bit of sleepThread is cleared, that is, sleepThread.isInterrupted() returns to false. Will continue the Main thread will continue to execute. Therefore, the interrupt operation can be regarded as a convenient way of interaction between threads. Generally, when the thread is terminated, the interrupt flag bit or the flag bit can have the opportunity to clean up resources. Compared with the arbitrary and direct termination of the thread, this method should be elegant and safe.

2.6、join

The join method can be regarded as a way of collaboration between threads. In many cases, the input of one thread may be very dependent on the output of another thread. This is like two good friends. One friend first walks in front and suddenly sees the other. A friend is left behind. At this time, he will wait for the friend in the same place, and when the friend catches up, the two will go hand in hand;

In fact, this way of collaboration between threads is also in line with real life. In the process of software development, after obtaining requirements from customers, it needs to be decomposed by requirements analysts. At this time, product development will continue to follow up;

If a thread instance A executes threadB.join(), its meaning is: the current thread A will wait for the threadB thread to terminate before threadA will continue to execute;

The following methods are provided for the join method:

public final synchronized void join(long millis)
public final synchronized void join(long millis, int nanos)
public final void join() throws InterruptedException

In addition to providing the join() method, the Thread class also provides a timeout waiting method. If the thread threadB has not ended within the waiting time, threadA will continue to execute after the timeout. The key to the source code of the join method is:

 while (isAlive()) {
    wait(0);
 }

It can be seen that the current waiting object threadA will be blocked until the end of the waiting object threadB, that is, when isAlive() returns false, the while loop will not end. When threadB exits, the notifyAll() method will be called to notify all waiting threads.

2.7. Daemon thread:

Java is divided into two kinds of threads: user threads and daemon threads .

Daemon thread is a supporting thread, because it is mainly used for background scheduling and supporting work in the program;

This means that when there is no non-Daemon thread in a Java virtual machine (there is no user thread), the Java virtual machine will exit;

The way to set the daemon thread : You can set the thread as a Daemon thread by calling Thread.setDaemon(true) .

Note: The Daemon thread is used to complete supporting work, but the finally block in the Daemon thread is not necessarily executed when the Java virtual machine exits. Therefore, operations such as releasing resources should not be executed in the finnaly block. Such operations are not safe.

2.8、yield:

public static native void yield(), this is a static method. Once executed, it will be the current thread giving up the CPU. However, it should be noted that the giving up of the CPU does not mean that the current thread is no longer running. In the competition, the current thread will continue to run after obtaining the CPU time slice;

In addition, the surrendered time slice will only be allocated to threads with the same priority as the current thread.

The difference between sleep and yield:

  • The sleep() and yield() methods are the same that the current thread will hand over processor resources, but the difference between them is that other threads can compete for the time slice handed over by sleep(), which means that they have the opportunity to obtain the current The time slice given by the thread;
  • The yield() method only allows threads with the same priority as the current thread to obtain the released CPU time slice.

2.9、wait。notify、notifyall:

Regarding EntrySet and WaitSet , please read my blog:

The wait()/notify() related methods are available for any Java object , because these methods are defined on the superclass java.lang.Object of all objects.

Classic case: wait and notify realize producer-consumer:

public class MyTest {
    /**
     * 仓库
     */
    private static class Storage {
        //仓库容量
        private static final int FULL_COUNT = 10;
        private LinkedList<Object> store = new LinkedList<>();

        public synchronized void push(Object object) {
            while (store.size() == FULL_COUNT) {
                System.out.println("仓库容量已满,请等待!");
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            store.add(object);
            System.out.println("产品入库 ThreadName:" + Thread.currentThread().getName() + " size:" + store.size());
            //通知所有的消费者
            notifyAll();
        }

        public synchronized void getOut() {
            while (store.size() == 0) {
                System.out.println("仓库已空,请等待!ThreadName:" + Thread.currentThread().getName() + " size:" + store.size());
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            store.remove(0);
            System.out.println("产品出库 ThreadName:" + Thread.currentThread().getName() + " size:" + store.size());
            //通知所有的生产者
            notifyAll();
        }
    }

    /**
     * 生产者
     */
    private static class Produce extends Thread {
        private Storage storage;

        public Produce(String name, Storage storage) {
            super(name);
            this.storage = storage;
        }

        @Override
        public void run() {
            while (true) {
                storage.push(new Object());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 生产者
     */
    private static class Consumer extends Thread {
        private Storage storage;

        public Consumer(String name, Storage storage) {
            super(name);
            this.storage = storage;
        }

        @Override
        public void run() {
            while (true) {
                storage.getOut();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        Storage storage = new Storage();
        //三个生产者
        for (int i = 0; i < 3; i++) {
            new Produce("Produce" + i, storage).start();
        }
        //三个消费者
        for (int i = 0; i < 3; i++) {
            new Consumer("Consumer" + i, storage).start();
        }
    }
}

2.10、sleep:

The public static native void sleep(long millis) method is obviously a static method of Thread. Obviously, it makes the current thread sleep at a specified time. The accuracy of the sleep time depends on the timer and scheduler of the processor;

It should be noted that if the current thread acquires the lock, the sleep method will not lose the lock . The sleep method is often compared with the Object.wait() method, which is often asked in interviews.

The main difference between sleep and wait:

  1. The sleep() method is a static method of Thread, and wait is an Object instance method
  2. The wait() method must be called in a synchronized method or synchronized block, that is , the object lock must have been acquired . The sleep() method does not have this restriction and can be used anywhere. In addition, the wait() method releases the held object lock, so that the thread enters the waiting pool and waits for the next resource acquisition. The sleep() method just gives up the CPU and does not release the object lock;
  3. The sleep() method will continue to execute if the CPU time slice is obtained again after the sleep time is reached, and the wait() method must wait for the Object.notift/Object.notifyAll notification before leaving the waiting pool and obtaining the CPU time slice again. Will continue to execute.

2.11、park、unpark:

The LockSupport class provides the basis for thread blocking and wakeup. At the same time, it has the unparalleled advantages of wait and notify in terms of competition conditions;

When using the combination of wait and notify, a thread must ensure that the thread has been executed to the wait waiting point before being notified by another thread. If the notify is missed, it may be waiting forever. In addition, notify cannot guarantee that a specified thread will be awakened. In contrast to LockSupport, because park and unpark have introduced a permission mechanism , the permission logic is: initially, permit is 0, block when permit is equal to 0 when pack is called, and return when permit is equal to 1, and set permit to 0. When calling unpark, try to wake up the thread and increase the permit by 1.

For the same thread, the sequence of park and unpark operations does not seem to affect the correct execution of the program . If the unpark operation is performed first, the permission is 1, and then the park operation is performed. At this time, because the permission is equal to 1, the execution is directly returned, and the blocking operation is not performed.

The difference between wait/notify and park/unpark: or the advantages of park/unpark

  • LockSupport's park and unpark combination truly decouples the synchronization between threads , no additional object variable storage state is needed, and synchronization locks are not required, wait and notify must ensure that there must be a lock to execute, and execute the notify operation to release After the lock is locked, the current thread is also thrown into the waiting queue of the object lock, and LockSupport does not need to consider the object, lock, waiting queue and other issues at all.

In fact, the function of LockSupport to block and wake up threads is dependent on sun.misc. Unsafe , which is a very low-level class . Those who are interested can check the information. For example , the function implementation of the park() method depends on UNSAFE.park(false , 0L), the unpark() method is implemented by  UNSAFE.unpark(thread) ;

note:

  • Synchronzed causes the thread to block, the thread will enter the BLOCKED state, and calling the LockSupprt method to block the thread will cause the thread to enter the WAITING state.

to sum up:

  • Thread blocking wakes up three cheap customers: sleep, wait/notify, park/unpark;

 

 

Guess you like

Origin blog.csdn.net/ScorpC/article/details/113806674