1. Multithreaded programming basics

Disclaimer: The content of the article is a study note about multithreading video watched on the Internet

1.1 Processes and threads

1.1.1 process

A process is the basic execution unit of an operating system.

1.1.2 thread

A thread is the smallest unit that an operating system can perform operations on. It is included in the process. A thread is a single sequential flow of control in a process, and multiple threads can be concurrent in a process, each performing a different task.

The difference between the two. Resources can be shared between threads, but not between processes. A variable in process A is inaccessible to process B, but is accessible between threads.

1.2 Thread Usage

  • Thread
Thread thread = new Thread() {
     @Override
     public void run(a) {
         System.out.println("Thread started!"); }}; thread.start();Copy the code
  • Runnable
Runnable runnable = new Runnable() {
     @Override
     public void run(a) {
         System.out.println("Thread with Runnable started!"); }}; Thread thread =new Thread(runnable);
thread.start();
Copy the code
  • The thread pool
Runnable runnable = new Runnable() {
 @Override
 public void run(a) {
 System.out.println("Thread with Runnable started!"); }}; Executor executor = Executors.newCachedThreadPool(); executor.execute(runnable);Copy the code
  • Callable and Future
    Callable<String> callable = new Callable<String>() {
        @Override
        public String call(a) {
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Done!"; }}; ExecutorService executor = Executors.newCachedThreadPool(); Future<String> future = executor.submit(callable);try {
        While (future.isdone ())
        String result = future.get();
        System.out.println("result: " + result);
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
Copy the code

1.3 Common API for Threads

1.3.1 currentThread method

Returns details about the thread on which the code is being invoked

1.3.2 isAlive

Is to determine whether the current thread is active. The active state is when the thread is started and running without completion. A thread is considered alive if it is running or ready to start running.

1.3.3 sleep method

Suspends the current thread of execution for the specified number of milliseconds.

1.3.4 getId method

Gets the unique identity of the current thread

1.4 Stopping a Thread

Stopping a thread means ending the operation being performed before the thread processing completes the task.

  1. Use exit flags to allow the thread to exit normally, that is, to terminate when the run method completes. Use a Boolean flag

  2. The stop method forces the thread to stop. (The stop method has been deprecated because it makes the program uncontrollable, not knowing exactly when the thread will stop. Another reason is that the locked object is “unlocked”, resulting in different data synchronization processing and data inconsistency.)

  3. Interrupt a thread with the interrupt method

    • Calling the interrupt method does not actually terminate the thread; it marks the current thread with a stop flag. The Thread class provides the interrupted method to test whether the current Thread has been interrupted, and the isInterrupted method to test whether the Thread has been interrupted.
    • The thread’s own interrupt method does not change state after being called, but the static interrupt method does, and the interrupt state changes to false after being called once
    • When is it good to call an interrupt method? Before time-consuming operation

1.5 Suspending a Thread

Suspend a thread using suspend and restart it using the resume method

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Demo19Thread t = new Demo19Thread();
        t.start();
        Thread.sleep(1000);

        t.suspend();    // Suspend the thread
        System.out.println("A=" + System.currentTimeMillis() + ", i=" + t.getI());
        Thread.sleep(1000);
        System.out.println("A=" + System.currentTimeMillis() + ", i=" + t.getI());

        t.resume();     // Resume suspended thread running
        Thread.sleep(1000);

        t.suspend();
        System.out.println("B=" + System.currentTimeMillis() + ", i=" + t.getI());
        Thread.sleep(1000);
        System.out.println("B=" + System.currentTimeMillis() + ", i="+ t.getI()); }}class Demo19Thread extends Thread{
    private long i = 0;

    public long getI(a){
        return i;
    }

    public void setI(long i){
        this.i = i;
    }

    @Override
    public void run(a) {
        while (true){ i++; }}}Copy the code

The suspend and resume methods are both obsolete.

1. When the suspend method suspends a thread, the lock is not released. As a result, other threads cannot hold the lock and enter the run method.

2. The suspend and resume methods are not synchronized and may cause dirty read environments.

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Demo22User user = new Demo22User();
        Thread t1 = new Thread(){
            @Override
            public void run(a) {
                user.updateUsernameAndPassword("b"."bb"); }}; t1.setName("A");
        t1.start();

        Thread.sleep(10);

        Thread t2 = new Thread(){
            @Override
            public void run(a) { user.printUseruserAndPassword(); }}; t2.start(); }}class Demo22User {
    private String username = "a";
    private String password = "aa";

    public void updateUsernameAndPassword(String username, String password){
        this.username = username;
        if ("A".equals(Thread.currentThread().getName())){
            System.out.println("Stop thread A");
            Thread.currentThread().suspend();
        }
        this.password = password;
    }

    public void printUseruserAndPassword(a){
        System.out.println("username=" + username + ", password="+password); }}Copy the code

1.6 yield method

The yield method gives up the current CPU resource so that another task can use it for execution. However, the abandonment time is uncertain. It is possible that the CPU time slice is acquired immediately after the abandonment.

1.7 Priority of the thread

In an operating system, threads can be prioritized, and the higher priority thread gets more CPU resources, so the CPU will perform the tasks in the higher priority thread object first. Setting thread priority helps the Thread scheduler determine which thread is selected to execute first the next time.

The setPriority method is used to set the priority of a thread. The PRIORITY ranges from 1 to 10. If the priority is set to less than 1 or more than 10, the JDK throws IllegalArgumentException. The JDK defaults to three priority constants, MIN_PRIORITY=1(minimum), NORM_PRIORITY=5(median, default), and MAX_PRIORITY=10(maximum).

Get the priority of the thread using the getPriority method.

The priority of A thread is inherited. For example, thread A starts thread B, and thread B has the same priority as thread A. High-priority threads are always mostly finished first, but this does not mean that high-priority threads are all finished. When thread priorities vary widely, the order in which the code is called does not matter who finishes first.

Thread priority also has “randomness,” which means that the higher-priority line doesn’t always finish first

1.8 Daemon Threads

There are two types of Java threads, user threads and daemon threads.

A daemon thread is a special thread, special in the sense that it will destroy itself when no user thread exists in the process. A typical example of a daemon thread is the garbage collector thread. When there are no user threads in the process, the garbage collector thread is no longer necessary and will be destroyed automatically. (Settings daemon thread must be set before call the start method, otherwise the JDK will produce IllegalThreadStateException)

2. Synchronization mechanism of threads

2.1 If multiple threads simultaneously read and write shared variables, data inconsistency may occur. Java programs rely on synchronized to synchronize threads, and when synchronized is used, it is important which object is locked. The lock acquired by synchronized is an object lock, rather than a piece of code or method as a lock. Therefore, that line first executes a method decorated with the synchronized keyword, which method holds the lock of the object that the method belongs to. Other threads can only wait, provided that multiple threads access the same object. If multiple threads access multiple objects, the JVM creates multiple object locks. Synchronized modifies ordinary methods to acquire the lock object this, while synchronized modifies static methods to acquire the bytecode of the current class

2.2 reentrant lock

The keyword synchronized has the function of reentrant, which means that with synchronized, a thread that has acquired an object lock can acquire the object lock again if it requests additional object locks.

Synchronized (class) blocks of code serve the same purpose as synchronized static methods

2.3 Automatic lock release

When code executed by a thread fails, the lock it holds is automatically released.

2.4 Synchronization does not have inheritance

In addition to synchronizing code blocks using syncrhonized(this), Java also supports “any object” as an “object lock” for synchronization. This “any object” large number is an argument to a member variable or method, using the format synchronized(not this object).

In the case of multiple threads holding an “object lock” on the same object, only one thread can execute the code in a synchronized(non-this object) block at a time. If you use a different object lock, the result of running is an asynchronous call, which runs interleaved.

2.6 String as object, because constant pool, the same string is the same object. Using an instance of a custom class as an object, the member variables of the class change, but the lock object does not change, because the memory address corresponding to the object instance does not change

2.7 a deadlock

It refers to a deadlock caused by multiple threads competing for resources during operation. When the threads are in this deadlock state, they cannot continue to run without external force.

package chap2;

public class Demo23 {
    public static void main(String[] args) throws InterruptedException {
        Demo23Thread t = new Demo23Thread();
        t.setFlag("a");

        Thread t1 = new Thread(t);
        t1.start();
        Thread.sleep(10);

        t.setFlag("b");
        Thread t2 = newThread(t); t2.start(); }}class Demo23Thread extends Thread{
    private String flag;    // flag, which controls how the code runs
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void setFlag(String flag){
        this.flag = flag;
    }

    @Override
    public void run(a) {
        try {
            if ("a".equals(flag)) {
                synchronized (lock1) {
                    System.out.println("flag=" + flag);
                    Thread.sleep(3000);
                    synchronized (lock2) {
                        System.out.println("Execute lock1=>lock2 order"); }}}else {
                synchronized (lock2){
                    System.out.println("flag=" + flag);
                    Thread.sleep(3000);
                    synchronized (lock1){
                        System.out.println("Execute in lock2->lock1"); }}}}catch(InterruptedException e){ e.printStackTrace(); }}}Copy the code

2.8 Volatile ensures visibility and order. But there is no guarantee that multiple threads can write simultaneously, for example x++, x++ is a two-step process,

int temp = x;
x = temp = 1;Copy the code

You can use AtomicInteger AtomicBoolean instead

  • In the singleton pattern, where volatile is used, the last instantiation step may be taken away before the initialization is complete. Volatile ensures that data is fully initialized on instantiation
AtomicInteger atomicInteger = new AtomicInteger(0); . atomicInteger.getAndIncrement();Copy the code

2.9 Synchronize and volatile

  1. Volatile is an easy implementation of thread synchronization, performs better than synchronized, and can only modify variables. Synchronized can modify methods and code blocks. With the update of JDK, the efficiency of synchronized has been greatly improved, and the usage of synchronized is still high in development

  2. Multithreaded access to volatile will not block, whereas synhcronized will block

  3. Volatile guarantees visibility, order, but not atomicity, while synchronized guarantees atomicity, and also indirectly guarantees visibility, because it synchronizes data in private and public memory.

  4. Volatile addresses the visibility of variables across multiple threads, while synchronized addresses the synchronization of access to resources across multiple threads

In summary, volatile addresses the visibility and ordering of variables across multiple threads (preventing instruction reordering), while synchronized addresses the synchronization of access to resources across multiple threads

2.10 the Lock/ReentrantReadWriteLock

When a block of code is locked, only one thread at a time can execute the block from multiple threads. In fact, if only one variable is read in the code block, multiple threads can access all of them. ReentrantReadWriteLock separates read locks from write locks

Lock lock = newReentrantLock(); . lock.lock();try {
 x++;
} finally {
 // Ensure that the lock is still released when the method terminates prematurely or an Exception occurs.
 lock.unlock();
}
Copy the code
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        Lock readLock = lock.readLock();
        Lock writeLock = lock.writeLock();
        private int x = 0;
        private void count (a) {
            writeLock.lock();
            try {
                x++;
            } finally{ writeLock.unlock(); }}private void print ( int time){
            readLock.lock();
            try {
                for (int i = 0; i < time; i++) {
                    System.out.print(x + "");
                }
                System.out.println();
            } finally{ readLock.unlock(); }}Copy the code

Conclusion:

  • The essence of thread-safety issues is that data errors occur when multiple threads access a common resource.
  • The essence of locking is that only one thread can access a resource at a time

3. Communication between threads

Threads are individual individuals within a program, but these individuals can become a whole if they are not processed. The communication between threads is to become one of the overall winning scheme, can be said to make the communication between threads, the interaction between threads will be more powerful, greatly improve the CPU reuse rate, but also enable programmers to each thread task in the process of processing effective control and supervision.

3.1 wait and notify

  1. Implementation of Wait/Notifty mechanism

The wait method is an Object method that places the current thread in a “pre-execution queue” and stops execution at the line of code where the wait is held until notified or interrupted. Before a wait method can be called, the thread must acquire the object lock on that object and call the wait method through the lock. This means that the wait method can only be called within a synchronized method or block of synchronized code. If the wait method is executed, the current thread lock is automatically released. When a wait method returns a thread to recompete with another thread for the lock.

package chap3;

public class Demo03 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new Demo03ThreadA(lock);
        t1.start();
        Thread.sleep(2000);
        Thread t2 = newDemo03ThreadB(lock); t2.start(); }}class Demo03ThreadA extends Thread{
    private Object lock;
    public Demo03ThreadA(Object lock){
        this.lock = lock;
    }

    @Override
    public void run(a) {
        try {
            synchronized (lock) {
                System.out.println("Thread A starts waiting in" + System.currentTimeMillis());
                lock.wait();
                System.out.println("Thread A finishes waiting in"+ System.currentTimeMillis()); }}catch(InterruptedException e){ e.printStackTrace(); }}}class Demo03ThreadB extends Thread{
    private Object lock;
    public Demo03ThreadB(Object lock){
        this.lock = lock;
    }

    @Override
    public void run(a) {
        try {
            synchronized (lock) {
                System.out.println("Thread B is ready to send a notification in" + System.currentTimeMillis());
                lock.notify();
                System.out.println("Thread B terminates to issue notification at" + System.currentTimeMillis());
                Thread.sleep(1000); }}catch(InterruptedException e){ e.printStackTrace(); }}}Copy the code

  • Wait and notify methods should be in synchronization method invocation, in front of the calling thread must acquire the object object lock, if did not obtain the proper lock also sell IllegalMonitorStateException. If more than one thread is waiting, the thread debugger randomly selects a thread in the wait state to notify it, and causes the waiting thread to acquire the object lock.
  • After executing notify, the current thread does not release the lock immediately, nor does a wait thread acquire the lock immediately. The lock is released only after the notify thread exits the Synchronize code block. Wait thread can obtain the lock only
  • When the first wait thread to acquire the object lock completes, it releases the object lock. If the object does not use the nofity statement again, the object handles the idle state. Other threads in the wait state, which have not been notified, continue to handle the blocked state until the object notifies the first time
  • The wait method automatically releases the lock and notify method does not release the lock. Notify method must execute the synchronization code to release the lock
  • When a thread is in wait state, calling the interrupt method on a thread object raises InterruptedException
  • The notify method is called to randomly wake up one thread at a time. NotifyAll () to wake up all threads
  • A method that takes a long argument waits to see if a thread object lock has been awakened for a certain amount of time, and the thread wakes up automatically if it has waited longer
public class Demo04 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new Demo04Thread(lock);
        t1.start();
        Thread.sleep(2000); t1.interrupt(); }}class Demo04Service{
    public void foo(Object lock){
        try{
            synchronized (lock){
                System.out.println("Ready to start waiting.");
                lock.wait();
                System.out.println("End of wait"); }}catch (InterruptedException e){
            System.out.println("Exception occurred because threads in wait state are interrupted"); e.printStackTrace(); }}}class Demo04Thread extends Thread{
    private Object lock;
    public Demo04Thread(Object lock){
        this.lock = lock;
    }

    @Override
    public void run(a) {
        Demo04Service service = newDemo04Service(); service.foo(lock); }}Copy the code

When will the lock be released?

  1. The lock is released when the block of synchronized code is executed.

  2. When an exception occurs during the execution of a synchronized code block, the thread terminates and the lock is released.

  3. During the execution of a synchronized code block, the wait method of the object on which the lock belongs is executed. The thread releases the object lock and the thread object enters the thread wait pool to be awakened.

3.2 Realization of producer/Consumer Model (one producer and one consumer)

public class Demo09 {
    public static void main(String[] args) {
        Object lock = new Object();
        Thread t1 = new Demo09Producer(lock);
        t1.start();
        Thread t2 = newDemo09Consumer(lock); t2.start(); }}class Demo09VO{
    public static String value = "";
}

/ / producer
class Demo09Producer extends Thread {
    private Object lock;
    public Demo09Producer(Object lock){
        this.lock = lock;
    }

    @Override
    public void run(a) {
        try{
            while(true) {
                synchronized (lock) {
                    if (!"".equals(Demo09VO.value)){
                        lock.wait();
                    }
                    String value = System.currentTimeMillis() + "_" + System.nanoTime();
                    System.out.println("Set has the value:"+ value); Demo09VO.value = value; lock.notify(); }}}catch(InterruptedException e){ e.printStackTrace(); }}}class Demo09Consumer extends Thread{
    private Object lock;
    public Demo09Consumer(Object lock){
        this.lock = lock;
    }

    @Override
    public void run(a) {
        try{
            while(true) {synchronized (lock){
                    if ("".equals(Demo09VO.value)){
                        lock.wait();
                    }
                    System.out.println("Get has the value:" + Demo09VO.value);
                    Demo09VO.value = ""; lock.notify(); }}}catch(InterruptedException e){ e.printStackTrace(); }}}Copy the code

If multiple producers and consumers are designed on the basis of this code, it is very likely that the execution process will be “suspended”, that is, all threads are in WAIT state

3.3 More production and more consumers

public class Demo10 {
    public static void main(String[] args) {
        Object lock = new Object();
        int size = 2;
        Thread[] producers = new Thread[size];
        Thread[] consumers = new Thread[size];
        for (int i = 0; i < size; i++) {
            char c = (char) ('A' + i);
            producers[i] = new Demo10Producer(lock);
            producers[i].setName("Producer" + c);

            consumers[i] = new Demo10Consumer(lock);
            consumers[i].setName("Consumer"+ c); producers[i].start(); consumers[i].start(); }}}class Demo10VO{
    public static String value = "";
}

/ / producer
class Demo10Producer extends Thread {
    private Object lock;
    public Demo10Producer(Object lock){
        this.lock = lock;
    }

    @Override
    public void run(a) {
        try{
            while(true) {
                synchronized (lock) {
                    if (!"".equals(Demo10VO.value)){
                        System.out.println(Thread.currentThread().getName() + "Waiting...");
                        lock.wait();
                    }
                    System.out.println(Thread.currentThread().getName() + "In production...");
                    String value = System.currentTimeMillis() + "_" + System.nanoTime();
                    Demo10VO.value = value;
                    //lock.notify();lock.notifyAll(); }}}catch(InterruptedException e){ e.printStackTrace(); }}}class Demo10Consumer extends Thread{
    private Object lock;
    public Demo10Consumer(Object lock){
        this.lock = lock;
    }

    @Override
    public void run(a) {
        try{
            while(true) {synchronized (lock){
                    if ("".equals(Demo10VO.value)){
                        System.out.println(Thread.currentThread().getName() + "Waiting...");
                        lock.wait();
                    }
                    System.out.println(Thread.currentThread().getName() + "In consumption...");
                    Demo10VO.value = "";
// lock.notify();lock.notifyAll(); }}}catch(InterruptedException e){ e.printStackTrace(); }}}Copy the code

Suppose that producer A is producing data, and the other three threads (producer B, consumer A, and consumer B) are all in the wait state. When producer A finishes producing, it wakes up one thread randomly, which just wakes up producer B. Producer B finds that there is data in ValueObject, so it enters the wait state (Producer A contests the lock, Producer B, consumer A, consumer B wait state), A reacquires the lock but finds that the value created has not been consumed, so it enters the wait state again. The result is that all four threads are in wait state. To solve this problem, use notifyAll to wake up all threads and ensure that the generated values are consumed.