Dark horse programmer concurrent programming notes -- basic operation and understanding of java threads

Posted by jk11uk on Thu, 27 Jan 2022 05:38:45 +0100

3. Basic operation of java process

3.1. Create process

Method 1: directly use Thread

// The parameter of the constructor is to assign a name to the thread. It is recommended to give a name to the thread (setName() is also acceptable)
Thread t1 = new Thread("t1") {
 @Override
 // The task to be executed is implemented in the run method
 public void run() {
 log.debug("hello");
 }
};
t1.start();

Method 2: use Runnable to cooperate with Thread

Separate [thread] from [task] (code to be executed). Thread represents thread and Runnable runnable task (code to be executed by thread) test2 java

// Create task object
Runnable task2 = new Runnable() {
 @Override
 public void run() {
 log.debug("hello");
 }
};
// Parameter 1 is the task object; Parameter 2 is the name of the thread. It is recommended to give the thread a name
Thread t2 = new Thread(task2, "t2");
t2.start();

After 1.8, lambda expression is used to simplify the writing method

// Create task object
Runnable task2 = () -> log.debug("hello");
// Parameter 1 is the task object; Parameter 2 is the thread name, which is recommended
Thread t2 = neaw Thread(task2, "t2");
t2.start();

It can also be a little simpler

// Parameter 1 is the task object; Parameter 2 is the thread name, which is recommended
Thread t2 = neaw Thread(() -> log.debug("hello"), "t2");
t2.start();

Shortcut key for simplifying lamba expression in idea: alt +enter

Summary

Method 1 combines threads and tasks. Method 2 separates threads and tasks. It is easier to use runnable to cooperate with high-level API s such as Thread pool. Runnable makes the task class separate from the Thread inheritance system and more flexible. By looking at the source code, we can find that method 2 is actually executed through method 1!

It should be noted that:

  • If the run() method of Thread is run directly, it is called by the main Thread

Method 3: FutureTask with Thread

FutureTask can receive parameters of Callable type, which is used to deal with the situation that there are returned results java

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // The third method of multithreading can return data or throw exceptions. The returned data needs to be received with get
        FutureTask futureTask = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("Multithreaded task");
                Thread.sleep(100);
                return 100;
            }
        });
        
  
        new Thread(futureTask,"My name").start();
        log.debug("Main thread");
        //{} indicates occupancy. The actual value is to buy your parameters and obtain the results. If necessary, you can get the execution result through the get method, which will block until the task returns the result.
        log.debug("{}",futureTask.get());
    }

Future is to cancel and query the execution results of specific Runnable or Callable tasks.

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Future provides three functions:

  1. Judge whether the task is completed;
  2. Ability to interrupt tasks;
  3. Be able to obtain task execution results.

3.3 methods of viewing process threads

windows

The task manager can view the process and the number of threads, and can also be used to kill the process

tasklist View process

taskkill Kill process

tasklist | findstr java View all java thread 

linux

ps -fe View all processes

ps -fT -p <PID> View a process( PID)All threads of

kill Kill process

top Press uppercase H Toggles whether threads are displayed

top -H -p <PID> View a process( PID)All threads of

ps -fe | grep java View all java process

Java

jps Command view all Java process

jstack <PID> View a Java Process( PID)Status of all threads

jconsole To see a Java Operation of threads in the process (graphical interface)

jconsole remote monitoring configuration

You need to run your java class as follows

java -Djava.rmi.server.hostname=`ip address` -Dcom.sun.management.jmxremote -
Dcom.sun.management.jmxremote.port=`Connection port` -Dcom.sun.management.jmxremote.ssl=Secure connection -
Dcom.sun.management.jmxremote.authenticate=Is it certified java class

Modify the / etc/hosts file to map 127.0.0.1 to the host name

If you want to authenticate access, you also need to do the following steps

Copy jmxremote Password file

Modify jmxremote Password and jmxremote The permission of access file is 600, that is, the file owner can read and write

Fill in controlRole (user name) and R & D (password) when connecting

example:

3.2 thread operation principle

3.2.1. Virtual machine stack and stack frame

Pseudo machine stack describes the memory model of Java method execution: when each method is executed, a stack frame will be created at the same time to store local variable table, operand stack, dynamic link, method exit and other information, which is private to the thread. When multithreading is used in Java, each thread will maintain its own stack frame! Each thread can only have one active stack frame, corresponding to the method currently executing

3.2.2. Thread Context Switch

For the following reasons, the cpu no longer executes the current thread, but executes the code of another thread

  • The cpu time slice of the thread runs out (each thread executes in turn. See the previous concept of parallelism)
  • garbage collection
  • Threads with higher priority need to run
  • The thread calls sleep, yield, wait, join, park, synchronized, lock and other methods

When a Context Switch occurs, the operating system needs to save the state of the current thread and restore the state of another thread. The corresponding concept in Java is the Program Counter Register, which is used to remember the execution address of the next jvm instruction and is private to the line

3.3 common methods of thread

3.3.1 start and run

Call start

    public static void main(String[] args) {
        Thread thread = new Thread(){
          @Override
          public void run(){
              log.debug("I am a new thread running");
              FileReader.read(fileName);
          }
        };
        thread.setName("New thread");
        thread.start();
        log.debug("Main thread");
    }

Output: the program runs on thread t1, and the call of contents in the run() method is asynchronous test4 java

11:59:40.711 [main] DEBUG com.concurrent.test.Test4 - Main thread
11:59:40.711 [New thread] DEBUG com.concurrent.test.Test4 - I am a new thread running
11:59:40.732 [New thread] DEBUG com.concurrent.test.FileReader - read [test] start ...
11:59:40.735 [New thread] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 3 ms

Call run

Put the thread of the above code start(); Change to thread run(); The output results are as follows: the program is still running in the main thread, and the call of the contents in the run () method is still synchronous

12:03:46.711 [main] DEBUG com.concurrent.test.Test4 - I am a new thread running
12:03:46.727 [main] DEBUG com.concurrent.test.FileReader - read [test] start ...
12:03:46.729 [main] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 2 ms
12:03:46.730 [main] DEBUG com.concurrent.test.Test4 - Main thread

Summary

Calling run() directly is to execute run() in the main thread. If no new thread is started, the main thread is used. Using start() is to start a new thread. The code in the run() method is executed indirectly through the new thread

3.3.2 sleep and yield

sleep

  1. Calling sleep will make the current thread enter the Timed Waiting state (blocking) from Running
  2. Other threads can use the interrupt method to interrupt the sleeping thread, and the interrupted thread will throw an InterruptedException exception [Note: the interrupted thread here is the sleeping thread, not the thread in other states]
  3. The thread after sleep may not be executed immediately (it needs to be allocated to cpu time slice)
  4. It is suggested to replace Thread's sleep() with TimeUnit's sleep() for better readability

Tip: sleep() of TimeUint

yield

  1. Calling yield will enable the current thread to enter the Runnable ready state from Running, and then schedule other threads.
  2. The specific implementation depends on the task scheduler of the operating system (that is, there may be no other thread executing. Although the yield method is called, it is useless)

3.3.3. thread priority

  • The thread priority will prompt the scheduler to schedule the thread first, but it is only a hint. The scheduler can ignore it. If the cpu is busy, the thread with high priority will get more time slices, but when the cpu is idle, the priority has little effect
  • Corresponding operation
thread1.setPriority(Thread.MAX_PRIORITY); //Set to highest priority

3.3.4. join method

  • Call t1. in main thread Join, the main thread will wait for the T1 thread to execute before continuing
private static void test1() throws InterruptedException {
    log.debug("start");
    Thread t1 = new Thread(() -> {
        log.debug("start");
        sleep(1);
        log.debug("end");
        r = 10;
    },"t1");
    t1.start();
    // t1.join(); 
    // If you don't add t1 Join(), at this time, the main thread will not wait for t1 thread to assign a value to R, and the main thread will directly output r=0
    // If you add t1 Join(), the main thread will wait until the t1 thread finishes executing (synchronous), r=10;
    log.debug("The result is:{}", r);
    log.debug("end");
}

The diagram is as follows:

Thinking: time to run while waiting for multiple results'

static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
 test2();
}
private static void test2() throws InterruptedException {
 //Thread t1
 Thread t1 = new Thread(() -> {
 sleep(1);
 r1 = 10;
 });
 //Thread t2   
 Thread t2 = new Thread(() -> {
 sleep(2);
 r2 = 20;
  });
    
 long start = System.currentTimeMillis();
 t1.start();
 t2.start();
 t1.join();
 t2.join();
 long end = System.currentTimeMillis();
 log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}

Because the waiting time of thread t1 is 1ms and the waiting time of line t2 is 2ms, thread t2 will not end until 1ms after thread t1 runs. Run for 2ms in total and output.

As shown in the figure:

After exchange:

static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
 test2();
}
private static void test2() throws InterruptedException {
 //Thread t1
 Thread t1 = new Thread(() -> {
 sleep(1);
 r1 = 10;
 });
 //Thread t2   
 Thread t2 = new Thread(() -> {
 sleep(2);
 r2 = 20;
  });
    
 long start = System.currentTimeMillis();
 t1.start();
 t2.start();
 t2.join();
 t1.join();
 long end = System.currentTimeMillis();
 log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}

After the thread t2join ends, it can output immediately. The total time is still two seconds.

Parameters in the join() function

At the same time, parameters can also be written in join(),

  • When the parameter is less than the actual running time, the main thread waits for the parameter time before starting action

    static int r1 = 0;
    static int r2 = 0;
    public static void main(String[] args) throws InterruptedException {
     test3();
    }
    public static void test3() throws InterruptedException {
     Thread t1 = new Thread(() -> {
     sleep(2);
     r1 = 10;
     });
     long start = System.currentTimeMillis();
     t1.start();
     // join end, wait for end
     t1.join(1500);
     long end = System.currentTimeMillis();
     log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }
    
  • When the parameter is greater than the actual running time, the main thread will run according to the actual running time.

static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
 test3();
}
public static void test3() throws InterruptedException {
 Thread t1 = new Thread(() -> {
 sleep(2);
 r1 = 10;
 });
 long start = System.currentTimeMillis();
 t1.start();
 //End of operation, wait for end
 t1.join(3000);
 long end = System.currentTimeMillis();
 log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}

3.3.5. interrupt

Interrupt is a unique interrupt method. It does not terminate the thread itself. It is often used together with the interrupt flag to make the thread termination more "elegant".

  • When interrupt interrupts a running thread, the interrupt flag becomes true
  • Interrupting a thread in sleep status will report the corresponding exception and will not change the interrupt flag to false

Interrupt the thread in sleep

private static void test1() throws InterruptedException {
 Thread t1 = new Thread(()->{
 sleep(1);
 }, "t1");
 t1.start();
 sleep(0.5);
 t1.interrupt();
 log.debug(" Interrupt state: {}", t1.isInterrupted());
}

output

java.lang.InterruptedException: sleep interrupted
 at java.lang.Thread.sleep(Native Method)
 at java.lang.Thread.sleep(Thread.java:340)
 at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
 at cn.itcast.n2.util.Sleeper.sleep(Sleeper.java:8)
 at cn.itcast.n4.TestInterrupt.lambda$test1$3(TestInterrupt.java:59)
 at java.lang.Thread.run(Thread.java:745)
21:18:10.374 [main] c.TestInterrupt - Interrupt state: false

Interrupt a running thread

private static void test2() throws InterruptedException {
 Thread t2 = new Thread(()->{
 while(true) {
 Thread current = Thread.currentThread();
 boolean interrupted = current.isInterrupted();
 if(interrupted) {
 log.debug(" Interrupt state: {}", interrupted);
 break;
 }
 }
 }, "t2");
 t2.start();
 sleep(0.5);
 t2.interrupt();
}
20:57:37.964 [t2] c.TestInterrupt - Interrupt state: true

Note: the difference between isInterrupted and interrupted

The former changes the break flag to true without automatically changing it to false, while the latter automatically changes it to false

3.4. The two-stage termination mode of termination mode (I) is realized by interruption

The two-phase termination pattern comes from the use of heap break method and break mark

The general operation is as follows:

To put it simply: use a monitoring thread to monitor all threads. During the running process, always check whether the interrupt flag of the current thread is true. When the interrupt flag is true, stop the thread.

And we can stop the thread through this.

If the current thread is interrupted during normal operation, we can get the above results. If it is interrupted during sleep, we need to manually change the flag to true

The actual code is as follows:

public class Test7 {
	public static void main(String[] args) throws InterruptedException {
		Monitor monitor = new Monitor();
		monitor.start();
		Thread.sleep(3500);
		monitor.stop();
	}
}

class Monitor {

	Thread monitor;

	/**
	 * Start monitor thread
	 */
	public void start() {
		//Set the wire controller thread to monitor the thread status
		monitor = new Thread() {
			@Override
			public void run() {
				//Start non-stop monitoring
				while (true) {
                    //Determine whether the current thread is interrupted
					if(Thread.currentThread().isInterrupted()) {
						System.out.println("Processing subsequent tasks");
                        //Terminate thread execution
						break;
					}
					System.out.println("Monitor running...");
					try {
						//Thread sleep
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
						//If you are interrupted during sleep, the interrupt flag will not be set to true. At this time, you need to reset the interrupt flag
						Thread.currentThread().interrupt();
					}
				}
			}
		};
		monitor.start();
	}

	/**
	 * 	Used to stop the monitor thread
	 */
	public void stop() {
		//Interrupt thread
		monitor.interrupt();
	}
}

3.5 interrupt park thread

Breaking the park thread is a method to stop the current thread on the park () line. It can be controlled by the interrupt flag. It takes effect when the interrupt flag is truepark() loses its function and false

private static void test3() throws InterruptedException {

Thread t1 = new Thread(() -> { log.debug("park..."); 

LockSupport.park(); log.debug("unpark..."); 

log.debug("Interrupt status:{}", Thread.currentThread().isInterrupted()); }, "t1"); 

t1.start(); sleep(0.5); 

t1.interrupt();
}

If the interrupted thread is not restored manually, the interrupt flag will not be restored by itself, and the isInterrupted() method will not be restored automatically, so we can use thread Interrupted() clears the interrupted state.

3.6. Methods not recommended

3.7. Main thread and daemon thread

By default, Java processes need to wait for all threads to finish running before they end. There is a special thread called a daemon thread. As long as other non daemon threads finish running, even if the code of the daemon thread is not executed, it will be forced to end.

log.debug("Start running...");

Thread t1 = new Thread(() -> {
 log.debug("Start running...");
 sleep(2);
 log.debug("End of operation...");
}, "daemon");

// Set this thread as a daemon thread
t1.setDaemon(true);

t1.start();

sleep(1);
log.debug("End of operation...");
//result
08:26:38.123 [main] c.TestDaemon - Start running... 
08:26:38.213 [daemon] c.TestDaemon - Start running... 
08:26:39.215 [main] c.TestDaemon - End of operation...

be careful

Garbage collector thread is a kind of daemon thread

Acceptor and Poller threads in Tomcat are daemon threads, so Tomcat will not wait for them to finish processing the current request after receiving the shutdown command

3.8 five states

At the operating system level, we can divide the operation of threads into five states:

3.9. Six states

According to the division in thread state(), we can divide threads into six states

Test of six states:

@Slf4j(topic = "c.TestState")
public class TestState {
    public static void main(String[] args) throws IOException {
        Thread t1 = new Thread("t1") {	// new status
            @Override
            public void run() {
                log.debug("running...");
            }
        };

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while(true) { // runnable status

                }
            }
        };
        t2.start();

        Thread t3 = new Thread("t3") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
        t3.start();

        Thread t4 = new Thread("t4") {
            @Override
            public void run() {
                synchronized (TestState.class) {
                    try {
                        Thread.sleep(1000000); // timed_waiting displays the blocking status
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t4.start();

        Thread t5 = new Thread("t5") {
            @Override
            public void run() {
                try {
                    t2.join(); // waiting status
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t5.start();

        Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (TestState.class) { // blocked status
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t6.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state {}", t1.getState());
        log.debug("t2 state {}", t2.getState());
        log.debug("t3 state {}", t3.getState());
        log.debug("t4 state {}", t4.getState());
        log.debug("t5 state {}", t5.getState());
        log.debug("t6 state {}", t6.getState());
    }
}

Concurrent programming core

Topics: Java intellij-idea