Java multithreading? What the hell?

Multithreading

Highly recommend teacher Liao Xuefeng’s website

The difference between threads and processes

In computers, we call a task a process. Multiple subtasks can be executed simultaneously within a process. We call subtasks threads.

That is: a process contains at least one thread.

Java multithreading

A Java program is a JVM process that uses a main thread to execute a main()method, main()and multiple threads can exist inside the method.

Create thread

Thread creation

public class Main{
    
    
	public static void main(String[] args) {
    
    
        Thread thread = new Thread();
        t.start(); //启动线程t
    }    
}

Use threads to perform tasks

method one

Make a Threadsubclass of a class and override run()methods.

public class Main{
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new ThreadTest();
        t.start();
    }
}

class ThreadTest extends Thread {
    
    
    @Override
    public void run() {
    
    
        System.out.println("Start!");
    }
}

After the start method is executed, the run method is automatically called to output Start.

Method Two

Through the Runnableinterface.

public class Main{
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new ThreadTest(new Runnable());
        t.start();
    }
}

class ThreadTest implements Runnable{
    
    
    @Override
    public void run() {
    
    
        System.out.println("Start!");
    }
}

nature

  1. As you can see, run()when calling the method, there is no t.run()way to use it.

    If you use this method, essentially just call a common Java method to print the statement to complete the task, and no new thread is created, and all operations are performed in the mainthread.

    ThreadThe start()method must be called to start a new thread. The private native void start0()method is called internally . nativeThe modifier indicates that this method is implemented by the C language code inside the JVM virtual machine.

  2. The threads are executed concurrently. That is, the scheduling order between threads cannot be determined by the program itself.

    such as:

    public class Main {
          
          
        public static void main(String[] args) {
          
          
            System.out.println("main run");
            Thread t = new Thread() {
          
          
                public void run() {
          
          
                    System.out.println("thread run");
                    System.out.println("thread end");
                }
            };
            t.start();
            System.out.println("main end");
        }
    }
    

    In addition to determining the output first main run, the output order of the sentences cannot be determined. To simulate the effect of concurrent execution, you can call a Thread.sleep()method to force the current thread to temporarily set a period of time.

Thread priority

Thread.setPriority(int n); // 级别1-10, 默认5

Note that threads with high priority will only be scheduled more frequently by the operating system, and priority cannot be set to ensure that threads with high priority will be executed first.

If you want to make a thread run after another thread ends, you can use join()methods.

class Main {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("Main Run");
        Thread t = new Thread() {
    
    
            public void run () {
    
    
                System.out.println("Thread Run");
                System.out.println("Thread End");
            }
        };
        try {
    
    
            t.join();
            // 可以指定时间,当指定时间后线程未结束则不再等待。
            // 如果在指定时间内完成,线程仍会继续等待,指定到达指定时间
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("Main End");
    }
}

Thread status

The Java thread has the following states:

  1. New: The newly created thread has not been executed yet;
  2. Runnable: the running thread is scheduled by the CPU at any time;
  3. Blocked: The running thread is suspended due to some operations being blocked;
  4. Waiting: running thread, waiting for some operation;
  5. Timed Waiting: The running thread sleep()waits for time due to the execution method;
  6. Terminated: The thread has been terminated because the run()method has finished executing.

In a Java program, a thread object can only call a start()method once .

After the start, thread Runnable, Blocked, Waiting, Timed Waitingbetween state switch until it becomes Terminateda state.

Reason for thread termination

  1. Normal termination: the run()method has been executed;
  2. Unexpected termination: the run()method encountered an uncaught exception;
  3. Forced termination: call a stop()method on the thread instance (not recommended).

Thread interruption

method one

In the process of a thread performing a task, we may need to interrupt this thread and execute other threads, then we need to use interrupt()methods.

class Main {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t = new ThreadTest();
        // t线程启动
        t.start();
        // 当前线程暂停, 执行t线程
        Thread.sleep(1000);
        // 暂停结束,二者并行, 打断t线程
        t.interrupt();
        // 等待t线程结束
        t.join();
        System.out.println("Main End");
    }
}

class ThreadTest extends Thread {
    
    
    @Override
    public void run() {
    
    
        // 新建p线程
        Thread p = new ThreadTest2();
        // p线程启动
        p.start();
        try {
    
    
            // 等待p线程结束
            p.join();
        } catch (InterruptedException e) {
    
    
            System.out.println("T was Interrupted!");
        }
        p.interrupt();
    }
}

class ThreadTest2 extends Thread {
    
    
    @Override
    public void run() {
    
    
        int n = 0;
        // 在未被中止前持续循环
        while (!isInterrupted()) {
    
    
            System.out.println(n++ + "asdf");
            try {
    
    
                // 当前线程暂停
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
//                System.out.println("P was Interrupted!");
                // 被中止后break跳出循环
                break;
            }
        }
    }
}

mainThe thread calls the t.interrupt()method to interrupt the tthread. At this time, the tthread is waiting for the pthread to end. The t.interrupt()method immediately ends the tthread and throws InterruptedExceptionan exception. Before the tthread ends, the pthread is p.interrupt()called to pinterrupt the thread.

If this operation is not performed, the pthread will continue to run and the JVM will not exit. Sometimes, we have to make some threads loop indefinitely, so when we want to exit, who will close these threads?

So there is daemon thread

Method Two

We can determine whether the thread continues to run by setting the flag bit.

class Main {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        // 注意这里是子类的引用
        ThreadTest t = new ThreadTest();
        // 启动线程t
        t.start();
        // 当前线程暂停
        Thread.sleep(1000);
        // 改变标志位的值
        t.running = false;
    }
}

class ThreadTest extends Thread {
    
    
    // 标志位
    public volatile boolean running = true;

    @Override
    public void run() {
    
    
        int n = 0;
        while (running) {
    
    
            System.out.println(n++ + "asdf");
        }
        System.out.println("T End");
    }
}

Note that a volatilekeyword pair is runningused for marking. The keyword is used to mark shared variables between threads to ensure that each thread can read the updated variable value.

Shared variables between threads

Why use this keyword?

This involves Java's memory model. In the JVM, the value of the variable is stored in the main memory. When a thread accesses a variable, it first obtains a copy and saves it in the working memory of the thread.

If the thread modifies the variable, the JVM willSometimeWrite the modified value to the main memory, thistimeIs uncertain! Therefore, after the variable is changed, the variable accessed by a thread is still the value of the variable before the change.

So we need to use keywords to volatiletell JVM:

  1. Every time a variable is accessed, the latest value of the main memory is always obtained;
  2. Every time a variable is modified, it is written to the main memory immediately.

Daemon thread

When a user thread loops infinitely, we cannot terminate the JVM normally, but we really need this infinite looping thread, such as the Java garbage collection thread, how to do it? Daemon thread.

Daemon threads are used to serve user threads. In JVM, after all user threads are finished, the virtual machine will exit regardless of whether there are daemon threads running.

Creation of daemon threads

Thread t = new ThreadTest();
t.setDaemon(true);
t.start();

nature

  1. You cannot set the started thread as a daemon thread, ie

    t.start();
    t.setDaemon(true); // 将会抛出异常
    

    Is wrong, IllegalThreadStateExceptionan exception will be thrown .

  2. The new thread generated in the daemon thread is also Daemonthe same, that is, it is also a daemon thread;

  3. The daemon thread cannot access inherent resources such as files. Because it may be interrupted at any time;

  4. Java's own multithreading framework, for example ExecutorService, converts daemon threads into user threads, so if you want to use background threads, you cannot use Java's thread pool.

Thread synchronization

When multiple threads are running at the same time, the scheduling of threads is determined by the operating system, so any thread may be suspended by the operating system at any instruction, and then continue to execute after a certain period of time. Then, if multiple threads read and write shared variables at the same time, there will be data inconsistencies.

Such as:

class Main {
    
    
    public static void main(String[] args) throws Exception {
    
    
        ThreadAdd add = new ThreadAdd();
        ThreadDec dec = new ThreadDec();
        add.start();
        dec.start();
        add.join();
        dec.join();
        System.out.println(Counter.count);
    }
}

class Counter {
    
    
    public static int cnt = 0;
}

class ThreadAdd extends Thread {
    
    
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            Counter.cnt++;
        }
    }
}

class ThreadDec extends Thread {
    
    
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            Counter.cnt--;
        }
    }
}

What is the result of this procedure? 0?

In fact, the result of each run is different.

Atomic manipulation

When reading and writing variables, you must ensure that the operation is an atomic operation, that is, an operation that cannot be interrupted.

The JVM specifies the following operations as atomic operations:

  1. Basic type assignment;
  2. Reference type assignment.

Lock and unlock

If the operation is a non-atomic operation, the operation may be interrupted and result in incorrect results during the simultaneous execution of several threads.

In order to avoid this situation, we need to use lock and unlock operations.

Locking and unlocking operations can ensure that several instructions are always executed in one thread, and no other threads will enter this instruction range. Even if the thread is interrupted during execution, other threads will not be able to enter this instruction range because they cannot obtain the lock. Only after the execution thread releases the lock, that is, after unlocking, other threads have the opportunity to obtain the lock and execute.

This kind of code block between locking and unlocking is called 临界区, at most one thread executes in the critical section at any time.

Locking and unlocking can be seen as implementing non-atomic operations into atomic operations. Easy to get, atomic operations do not require locking.

Implementation of lock and unlock

We synchronizedlock an object through keywords.

which is:

  1. Find out the thread code block that modifies the shared variable;
  2. Choose a shared instance as the lock;
  3. Use synchronized(lockObject) {...}.

An example is this:

synchronized(lock) {
    
     //加锁
    n += 1;
} //自动解锁

After the above code is locked, it is rewritten as:

class Main {
    
    
    public static void main(String[] args) throws Exception {
    
    
        ThreadAdd add = new ThreadAdd();
        ThreadDec dec = new ThreadDec();
        add.start();
        dec.start();
        add.join();
        dec.join();
        System.out.println(Counter.count);
    }
}

class Counter {
    
    
    public static final Object lock = new Object();
    public static int cnt = 0;
}

class ThreadAdd extends Thread {
    
    
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            synchronized(Counter.lock) {
    
    
                Counter.cnt++;
            }
        }
    }
}

class ThreadDec extends Thread {
    
    
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            synchronized(Counter.lock) {
    
    
                Counter.cnt--;
            }
        }
    }
}

The code:

synchronized(Counter.lock) {
//....
}

Among them, the Counter.lockinstance is used as the lock, and the two threads obtain the lock in the critical section before performing the operation.

After the execution ends and the statement block ends, the lock is automatically released.

Reference

  1. https://www.liaoxuefeng.com/wiki/1252599548343744/1255943750561472
  2. https://zhuanlan.zhihu.com/p/28049750

Guess you like

Origin blog.csdn.net/qq_45934120/article/details/107180871