Java Concurrent Programming basics ① – Threads

Reprint please indicate the source!

What is a thread

Process is a code in the data set of a running activity, is the system for resource allocation and scheduling of the basic unit, thread is a process of execution path, a process at least one thread, multiple threads in the process share the process of resources.

The operating system allocates resources to processes, but CPU resources are allocated to threads, because threads are the basic unit of CPU allocation.

In Java, for example, when we start a main function, we actually start a JVM process, and the thread on which main is located is a thread of that process, also known as the main thread. There are multiple threads in a JVM process that share the heap and method area resources of the process, but each thread has its own program counter and stack area.

Thread creation and execution

There are three ways to create a Java thread:

  1. Inherit Thread and override the run method

  2. Implement the Run method of the Runnable interface

  3. Use FutureTask

Specific code examples are not detailed, too basic, will feel in the water.

Take the FutureTask approach, which implements the Run method of the Runnable interface, as can be seen from its inheritance structure.

Neither method gets the result of the task, but Futuretask does.

Thread notification and wait

3.1 wait () method

The effect of wait() is that the calling thread is blocked and suspended until one of the following conditions occurs:

  • Other threads call notify() or notifyAll() on the shared object (continue)

  • Another thread calls its interrupt() method, which returns InterruptedException

If calling wait () method of thread without prior access to the object of the monitor lock, the calling thread will be thrown IllegalMonitorStateException anomalies. When the thread calls wait(), it releases the monitor lock on the object.

So how can a thread acquire a monitor lock for a shared variable?

  1. Execute the synchronized synchronization block, using the shared variable as an argument.

    synchronized(Shared variable) {// TODO
    }
    Copy the code
  2. Call the synchronized method of the shared variable (synchronized modification)

    synchronized void sum(int a, int b) {
        // TODO
    }
    Copy the code

3.2 notify() / notifyAll()

A thread calling notify() on a shared object wakes up a thread calling wait(…) on that shared variable. Thread that is suspended after a series of methods.

It is worth noting:

  • There may be multiple threads waiting on a shared variable, and notify() is random about which thread is waiting;
  • Awakened thread not immediately return and continue from the wait method, it must be in after the monitor lock access to the Shared object can be returned, is awakened threads it released after the monitor lock on the Shared variables, awakened the thread does not necessarily will get to the Shared object monitor lock, this is because the thread needs to be competitive with other threads the lock, Execution can continue only if the thread has contended for the monitor lock on the shared variable;

The notifyAll() method wakes up all threads suspended on the shared variable due to calls to the WAIT series of methods.

3.3 instance

The classic example is that of producers and consumers

public class NotifyWaitDemo {

    public static final int MAX_SIZE = 1024;
    // Share variables
    public static Queue queue = new Queue();

    public static void main(String[] args) {
        / / producer
        Thread producer = new Thread(() -> {
            synchronized (queue) {
                while (true) {
                    // Suspend the current thread (producer thread)
                    // Also, release the monitor lock through the queue so that the consumer object acquires the lock and performs the consumption logic
                    if (queue.size() == MAX_SIZE) {
                        try {
                            queue.wait();
                        } catch(InterruptedException e) { e.printStackTrace(); }}// Idle generates the element and notifies the consuming threadqueue.add(); queue.notifyAll(); }}});/ / consumer
        Thread consumer = new Thread(() -> {
            synchronized (queue) {
                while (true) {
                    // Suspend the current thread (consumer thread)
                    // Also, release the monitor lock through the queue, allowing the producer object to acquire the lock and execute the production logic
                    if (queue.size() == 0) {
                        try {
                            queue.wait();
                        } catch(InterruptedException e) { e.printStackTrace(); }}// Idle consumes the element and notifies the production threadqueue.take(); queue.notifyAll(); }}}); producer.start();try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        consumer.start();
    }

    static class Queue {

        private int size = 0;

        public int size(a) {
            return this.size;
        }

        public void add(a) {
            // TODO
            size++;
            System.out.println("Execute add operation, current size:" +  size);
        }

        public void take(a) {
            // TODO
            size--;
            System.out.println("Execute take operation, current size:"+ size); }}}Copy the code

3.4 Why wait()/notify()/notifyAll() is defined in the Object class?

Since Thread inherits from Object, Thread can also call three methods. Wait and wake must be the same lock. The lock can be any object, so methods that can be called by any object are defined in the Object class.

Join () – waits for thread execution to terminate

Application scenario: Wait for several tasks to be completed before continuing. For example, multiple threads load resources.

public static void main(String[] args){... thread1.join(); thread2.join(); System.out.println("all child thread over!");
}
Copy the code

The main thread will block after calling thread1.join(), wait for thread1 to complete, call thread2.join(), wait for Thread2 to complete (possibly), and so on, and eventually wait for all child threads to finish before returning. If another thread calls the interrupt() method of the blocked thread, the blocked thread returns with InterruptedException.

4.1 instance

Give an example to help understand.

public class JoinExample {

    private static final int TIMES = 100;

    private class JoinThread extends Thread {

        JoinThread(String name){
           super(name);
        }

        @Override
        public void run(a) {
            for (int i = 0; i < TIMES; i++) {
                System.out.println(getName() + ""+ i); }}}public static void main(String[] args) {
        JoinExample example = new JoinExample();
        example.test();
    }

    private void test(a) {
       for (int i = 0; i < TIMES; i++) {
           if (i == 20) {
               Thread jt1 = new JoinThread("Child thread 1");
               Thread jt2 = new JoinThread("Child thread 2");
               jt1.start();
               jt2.start();
               The main thread calls the JT thread's join() method
               // The main thread must wait until JT finishes executing before it executes down
               try {
                   jt1.join();
                   jt2.join();
                   // join(long mills) - Wait until the thread that was joined has not executed
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           System.out.println(Thread.currentThread().getName() + ""+ i); }}}Copy the code

5. Thread sleep

Sleep () causes the thread to temporarily cede execution for a specified time, that is, not participate in CPU scheduling, but does not release the lock.

When the specified sleep time is up, the function returns normally, the thread is in the ready state, and then participates in the CPU scheduling. After obtaining CPU resources, the thread can continue to run.

If the thread is interrupted during sleep by another thread calling its interrupt() method, the thread returns with InterruptedException where the sleep method was called.

** * Thread B blocks until thread A wakes up and exits the Synchronize block to release the lock. ** ** ** *@author Richard_yyf
 * @version1.0 2020/3/12 * /
public class ThreadSleepDemo {

    
    private static final Object obj = new Object();

    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            // Obtain the obj monitor lock
            synchronized (obj) {
                try {
                    System.out.println("child thread A is in sleep");
                    Thread.sleep(10000);
                    System.out.println("child thread A is awake");
                } catch(InterruptedException e) { e.printStackTrace(); }}}); Thread threadB =new Thread(() -> {
            // Obtain the obj monitor lock
            synchronized (obj) {
                try {
                    System.out.println("child thread B is in sleep");
                    Thread.sleep(10000);
                    System.out.println("child thread B is awake");
                } catch(InterruptedException e) { e.printStackTrace(); }}}); threadA.start(); threadB.start(); }}Copy the code

-yield ()

When a thread calls yield, it actually implies to the thread scheduler that the current thread is requesting to yield its CPU usage (telling the thread scheduler to proceed with the next round of thread scheduling), but the thread scheduler can ignore the hint unconditionally.

We know that the operating system allocates a time slice for each Thread to occupy the CPU. Normally, when a Thread has used up its allotted time slice, the scheduler starts scheduling the next round of threads. When a Thread calls yield, the static method of the Thread class, The thread scheduler is telling the thread scheduler that it does not want to use the unused portion of its allotted time slot, implying that the thread scheduler can start the next thread schedule now.

Usually using this method, when debugging or test this method may help repetition problems due to concurrent competitive conditions, the concurrency control or may be useful in the design, in Java. Util. Concurrent. The locks the inside of the bag lock when they see the use of the method

The difference between sleep and yield is that when a thread calls sleep, the calling thread is blocked and suspended for a specified period of time, during which time the thread scheduler does not schedule the thread. When yield is invoked, the thread simply gives up its remaining slice of time. Instead of being blocked and suspended, it is in a ready state that the thread scheduler can schedule to be executed by the current thread the next time.

7. Thread interruption

Many people look at the interrupt() method and think that “interrupting” a thread means stopping it. In fact, this is not what the interrupt() method does at all. It is more like sending a signal that changes an identifier bit property of the thread (the interrupt identifier), and how it responds to that signal is uncertain (there can be different processing logic). Most of the time, the interrupt() method is called not to stop a thread, but to keep it running.

An official statement:

Thread interrupt in Java is a cooperative mode between threads. The execution of the thread can not be terminated directly by setting the interrupt flag of the thread, but the interrupted thread will handle it according to the interrupt state.

7.1 void interrupt ()

Set the thread’s interrupt flag to true and return immediately, but the thread is not actually interrupted and continues to execute; If a thread is blocked and suspended because it called the WAIT, JOIN, or sleep methods, other threads calling the interrupt() method of that thread will return with InterruptedException.

7.2 Boolean isInterrupted ()

Checks if the thread is interrupted, returning true if it is, and false otherwise.

	public boolean isInterrupted(a) {
        // Passing false indicates that the interrupt flag is not cleared
        return isInterrupted(false);
    }
Copy the code

7.3 Boolean interrupted ()

This checks whether the current thread has been interrupted and returns the same value as isInterrupted(), except that the interrupt flag is cleared if the current thread has been interrupted. This method is static and inside gets the interrupt flag of the current calling thread rather than the instance object that called the interrupted() method.

    public static boolean interrupted(a) {
        / / static method
        // true - Clears the terminal flag
        // currentThread
        return currentThread().isInterrupted(true);
    }
Copy the code

Thread context switch

As we all know, in multithreaded programming, the number of threads is generally greater than the number of cpus, and each CPU can only be used by one thread at a time.

In order to make the user feel that multiple threads are executing at the same time, the CPU resource allocation adopts the time slice rotation strategy, that is, each thread is allocated a time slice, and the thread occupies the CPU to execute the task within the time slice. When the current thread runs out of time slices, it becomes ready and frees up the CPU for other threads. This is a context switch for threads.

Thread deadlocks

Deadlock refers to the phenomenon that two or more threads are waiting for each other during execution due to competing for resources. Without external force, these threads will wait for each other and cannot continue to run.

As shown in the figure above, thread A holds resource 2 and wants to apply for resource 1, thread B holds resource 1 and wants to apply for resource 2, and the two threads wait for each other to form A deadlock state.

A deadlock can occur under four conditions:

  • Mutually exclusive: A thread can use a resource exclusively, that is, only one thread can occupy the resource at the same time. If another thread requests the resource at this point, the requester can only wait until the thread holding the resource releases the resource.
  • Request and hold condition: a thread that has already held at least one resource makes a request for a new resource, but the new resource has been occupied by another thread. Therefore, the current thread is blocked, but the blocked thread does not release the acquired resource.
  • Inalienable condition: A thread cannot preempt a resource until it uses it up. The thread releases the resource only after it uses it up.
  • Loop waiting condition: when deadlock occurs, there must be a thread-resource ring chain, namely thread set {T0, T1, T2… T0 in Tn} is waiting for a resource to be occupied by T1, T1 is waiting for a resource to be occupied by T2… Tn is waiting for resources already occupied by T0.

Daemon threads and user threads

Java threads fall into two categories,

  • Daemon threads
  • User thread (user thread)

The main function is called when the JVM starts, and the thread in which main is called is a user thread, and many daemon threads, such as the GC thread, are also started inside the JVM.

The difference between daemon threads and user threads is that daemon threads do not affect JVM exit, and the JVM exits normally when the last user thread terminates.

So, if you want the JVM to end as soon as the main thread ends, you can create the thread as a daemon thread, or if you want the child thread to continue working after the main thread ends and then the JVM process ends, you can set the child thread as a user thread.

For example, in Tomcat’s NIO implementation NioEndpoint class, a set of receiving threads is opened to accept user connection requests and a set of processing threads to process user requests.

	/** * Start the NIO endpoint, creating acceptor, poller threads. */
    @Override
    public void startInternal(a) throws Exception {

        if(! running) {/ /... omit

            // Start Poller Threads processing threads
            pollers = new Poller[getPollerThreadCount()];
            for (int i=0; i<pollers.length; i++) {
                pollers[i] = new Poller();
                Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
                pollerThread.setPriority(threadPriority);
                pollerThread.setDaemon(true); // 
                pollerThread.start();
            }
			// Start the receiving threadstartAcceptorThreads(); }}protected final void startAcceptorThreads(a) {
        int count = getAcceptorThreadCount();
        acceptors = new Acceptor[count];

        for (int i = 0; i < count; i++) {
            acceptors[i] = createAcceptor();
            String threadName = getName() + "-Acceptor-" + i;
            acceptors[i].setThreadName(threadName);
            Thread t = new Thread(acceptors[i], threadName);
            t.setPriority(getAcceptorThreadPriority());
            t.setDaemon(getDaemon()); // The default is truet.start(); }}Copy the code

In the code above, both the receiving and processing threads are daemons by default, which means that tomcat will die as soon as it receives shutdown and no other user threads are present, rather than waiting for the processing thread to finish processing the current request.

summary

This article covers the basics of Java threads. In the next article, I plan to write about the Life cycle of Java threads, their states, and the flow of states. Because I feel that most articles on domestic websites are not clear about this part, I will try to write an article by combining pictures, words and specific codes. Interested partners can pay attention to it.

If this article is helpful to you, please give me a thumbs up. This is my biggest motivation 🤝🤝🤗🤗.

reference

  • The Beauty of Concurrent Programming in Java