Java in-depth learning: multithreaded detailing

Multi-threading purpose: There are many different paths to execute programs at the same time, so as to improve the efficiency of program operation.

Multithread applications: database connection pool, multithreaded file download, etc.

 

Note: Using multithreading in file downloading can't improve speed.

In a process, there must be a main thread

 

Beginning with the basics, the use of multithreading is as follows:

1. Inherit the Thread class: (not recommended)

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        //Write threaded execution code
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.start();
    }
}

Note: TheadDemo calls the start method; if you call the run method, it's essentially single-threaded.

2. Implement Runnable interface:

public class ThreadDemo implements Runnable {
    @Override
    public void run() {
        //Write threaded execution code
        System.out.println("demo");
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        new Thread(threadDemo).start();
    }
}

 

3. Anonymous inner classes

public class ThreadDemo {
    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                //Write threaded execution code
            }
        }.start();
    }
}

Java 8 can be abbreviated as this

public class ThreadDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            //Write threaded execution code
        }).start();
    }
}

 

The state of multithreading:

1. New state: before calling the start method

2. Ready state: call the start method and wait for the CPU to allocate execution rights

3. Running state: Executing code in run methods

4. Death status: run method is completed

5. Blocking state: By calling wait or sleep methods, threads become blocked and blocked states can be directly turned into ready States

 

Daemon threads:

In Java programs, there are main threads and GC threads (for garbage collection). When the main threads die, the GC threads will die and be destroyed at the same time.

This thread destroyed with the main thread is the daemon thread.

Non-daemon threads: The state of threads is independent of the main thread

User threads: All the above three ways create user sites, which are created by main threads and non-daemon threads.

 

Example:

public class ThreadDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            for (int i = 0; i < 30; i++) {
                try {
                    Thread.sleep(300);
                    System.out.println("Sub thread i:" + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        for (int i = 0; i < 5; i++) {
            System.out.println("Main thread i:" + i);
        }
        System.out.println("The main thread has finished executing");
    }
}

Observing the output, we find that after the printer thread has finished executing, it is still printing the execution information of the sub-thread.

 

Only the sub-threads need to be set up to become daemon threads:

public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                System.out.println("Sub thread i:" + i);
            }
        });
        thread.setDaemon(true);
        thread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("Main thread i:" + i);
        }
        System.out.println("The main thread has finished executing");
    }
}

Observation of the output shows that the sub-thread has not been printed to 999, and the program has ended.

 

join method:

A thread calls the join method of B thread, then A waits for B to finish executing (A releases CPU execution right)

 

Example: The main thread lets the sub-threads execute before executing

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 60; i++) {
                System.out.println("Sub thread i:" + i);
            }
        });
        thread.start();
        thread.join();
        for (int i = 0; i < 10; i++) {
            System.out.println("Main thread i:" + i);
        }
        System.out.println("The main thread has finished executing");
    }
}

Observation output found that: sub-thread printing 59 before starting the main thread printing

 

Thread security issues:

Thread security issues occur when multiple threads share the same global variable and do write operations.

 

Simulated Thread Safety: A Classic Case of Station Ticket Selling

public class ThreadDemo implements Runnable {
    //A total of 100 tickets
    private int count = 100;

    @Override
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(100);
                sale();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void sale() {
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + "Sale No." + (100 - count + 1) + "Zhang ticket");
            count--;
        }
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        Thread t1 = new Thread(threadDemo, "Window 1");
        Thread t2 = new Thread(threadDemo, "Window 2");
        t1.start();
        t2.start();
    }
}

Observed output: many tickets are sold repeatedly

 

Thread security problem solving:

1. Use the synchronized keyword in the salt method

Principle: when a thread enters the method, it will automatically acquire the lock. Once a thread gets the lock, the other thread will wait until the thread code is finished and release the lock.

Disadvantage: Reducing program efficiency, each execution of this method needs to be judged.

    private synchronized void sale() {
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + "Sale No." + (100 - count + 1) + "Zhang ticket");
            count--;
        }
    }

 

2. Using Synchronized Code Blocks

public class ThreadDemo implements Runnable {
    //A total of 100 tickets
    private int count = 100;

    private final Object object = new Object();

    @Override
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(100);
                sale();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void sale() {
        synchronized (object) {
            if (count > 0) {
                System.out.println(Thread.currentThread().getName() + "Sale No." + (100 - count + 1) + "Zhang ticket");
                count--;
            }
        }
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        Thread t1 = new Thread(threadDemo, "Window 1");
        Thread t2 = new Thread(threadDemo, "Window 2");
        t1.start();
        t2.start();
    }
}

Observing Output: Problem Solving

 

Note: There are still problems if it is written like this.

    public static void main(String[] args) {
        ThreadDemo threadDemo1 = new ThreadDemo();
        ThreadDemo threadDemo2 = new ThreadDemo();
        Thread t1 = new Thread(threadDemo1, "Window 1");
        Thread t2 = new Thread(threadDemo2, "Window 2");
        t1.start();
        t2.start();
    }

 

At this point, you need to add a static keyword to the global variable: share the same lock

    private static int count = 100;

    private static final Object object = new Object();

Observing Output: Problem Solving

 

Multithread deadlock problem:

Scene: beginners like to join synchronized everywhere, so nesting synchronized in synchronized is easy to produce deadlock.

Reasons: A thread gets lock 2, and now needs lock 1; B thread takes lock 1, and now needs lock 2; A thread will not release lock 2 without lock 1; B thread will not release lock 1 without lock 2

 

ThreadLocal class:

What is ThreadLocal: providing local variables to each thread

Principle: the bottom is a collection of Map, which gets the current thread, then calls the Map's put and get methods.

Initialization:

    public static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

Obtain:

        threadLocal.get();

Set up:

        threadLocal.set(count);

 

Multithread features:

1. atomicity

2. visibility

3. orderliness

 

Java Memory Model (JMM):

JMM determines whether one thread can be visible when it writes to a shared variable

Main memory: variables for shared storage

Local memory: copies of shared variables

 

The fundamental principle of thread safety problems: shared variables are stored in main memory, and each thread has local memory. For example, if I store count=100 in main memory, then both local memory stores count=100 copies. At this point, the two threads operate on the shared variable count-1 at the same time. First, the two threads need to count-1 in local memory, and then refresh to main memory. Thread security issues arise!

 

Volatile keywords:

An example:

class ThreadTest extends Thread {
    public boolean flag = true;

    @Override
    public void run() {
        System.out.println("Thread start");
        while (flag) {

        }
        System.out.println("Thread end");
    }

    public void setRunning(boolean flag) {
        this.flag = flag;
    }
}

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        ThreadTest threadTest = new ThreadTest();
        threadTest.start();
        Thread.sleep(3000);
        threadTest.setRunning(false);
        System.out.println("flag Change to false");
        Thread.sleep(3000);
        System.out.println("flag:" + threadTest.flag);
    }
}

Print as follows:

Thread start
Change flag to false
flag:false

Then the program gets stuck.

 

Why has the flag been changed to false, and the child thread has entered the while loop?

Because: the main thread has changed the flag, and has not yet brushed into the main memory, the sub-thread has been reading the variables in the local memory.

 

Solution: Just add volatile keywords

Function: Update the modified value to main memory immediately to ensure that other threads are visible to the variable

    public volatile boolean flag = true;

Print as follows:

Thread start
Change flag to false
Thread end
flag:false 

Note: volatile guarantees only visibility, not thread security

 

Scenario: Looking at the mainstream framework, you can see that volatile keywords are added to variables that are shared globally.

 

Synchronized and Volatile keywords are different:

Volatile guarantees visibility, does not guarantee atomicity, that is, does not guarantee thread safety, prohibits reordering

Synchronized guarantees both atomicity and thread security without forbidding reordering

 

Reordering:

Concept: CPU optimizes code without reordering dependencies

What is dependency?

            int a = 1;
            int b = 2;
            int c = a + b;

c depends on a, B. c is related to a and B. c must be executed after a and b, while a and B may not be executed in the same order.

So when the code is executed, it is possible to execute int b = 2 first rather than int a = 1.

But the results performed here will not change.

 

Note: Generally, you only encounter reordering problems in multithreading

Solution of reordering problem: adding volatile keyword

 

Communication between threads:

Many threads are dealing with the same resource, but the tasks of threads are different. Threads can effectively utilize resources by certain means.

This means waiting for wake-up mechanism, also known as communication between threads

Methods involved: wait(), notify()

 

Example:

Two threads, one input and one output

package demo;

public class Resource {
    public String name;
    public String sex;
}

Input threads:

package demo;

public class Input implements Runnable {
    private Resource r = new Resource();

    public void run() {
        int i = 0;
        while (true) {
            if (i % 2 == 0) {
                r.name = "Zhang San";
                r.sex = "male";
            } else {
                r.name = "Li Si";
                r.sex = "female";
            }
            i++;
        }
    }

}

Output threads:

package demo;

public class Output implements Runnable {
    private Resource r = new Resource();
    public void run(){
        while (true) {
            System.out.println(r.name+"..."+r.sex);
        }
    }
}

Test class:

package demo;

public class ThreadDemo {
    public static void main(String[] args) {
        Input in = new Input();
        Output out = new Output();
        Thread tin = new Thread(in);
        Thread tout = new Thread(out);
        
        tin.start();
        tout.start();
    }
}

 

When it runs, it finds that the output is null...null.

Because the Resource objects created in the input and output threads make a difference

 

Solve the null problem:

package demo;

public class Input implements Runnable {
    private Resource r;
    
    public Input(Resource r){
        this.r = r;
    }

    public void run() {
        int i = 0;
        while (true) {
            if (i % 2 == 0) {
                r.name = "Zhang San";
                r.sex = "male";
            } else {
                r.name = "Li Si";
                r.sex = "female";
            }
            i++;
        }
    }

}
package demo;

public class Output implements Runnable {
    private Resource r;
    
    public Output(Resource r){
        this.r = r;
    }
    
    public void run(){
        while (true) {
            System.out.println(r.name+"..."+r.sex);
        }
    }
}
package demo;

public class ThreadDemo {
    public static void main(String[] args) {
        
        Resource r = new Resource();
        
        Input in = new Input(r);
        Output out = new Output(r);
        Thread tin = new Thread(in);
        Thread tout = new Thread(out);
        
        tin.start();
        tout.start();
    }
}

 

Another problem was found after operation:

The output includes: Zhang three... Female or Li Si... Male, gender error.

Causes:

After assigning Zhang Sanhe and Men, they continue to assign Li Si and women. Before they have time to assign women, they enter the output thread. At this time, they will output Li Si.... Men.

 

So I think of adding synchronization:

    public void run() {
        int i = 0;
        while (true) {
            synchronized (this) {
                if (i % 2 == 0) {
                    r.name = "Zhang San";
                    r.sex = "male";
                } else {
                    r.name = "Li Si";
                    r.sex = "female";
                }
                i++;
            }
        }
    }
    public void run() {
        while (true) {
            synchronized (this) {
                System.out.println(r.name + "..." + r.sex);
            }
        }
    }

 

However, the problem has not been solved:

Reason:

Synchronization here is not working, it's not a lock.

 

Solution:

Use a common lock.

public void run() {
        int i = 0;
        while (true) {
            synchronized (r) {
                if (i % 2 == 0) {
                    r.name = "Zhang San";
                    r.sex = "male";
                } else {
                    r.name = "Li Si";
                    r.sex = "female";
                }
                i++;
            }
        }
    }
    public void run() {
        while (true) {
            synchronized (r) {
                System.out.println(r.name + "..." + r.sex);
            }
        }
    }

This is the normal output.

However, there is still a problem. We hope that Zhang San and Li Si will appear alternately. One Zhang San and one Li Si will still appear randomly. Big pieces of Zhang San or Li Si will appear randomly.

 

 

Solution:

Let the input thread assign value first, then let the output thread output, and let the input thread wait, do not allow the assignment of Lisi, wait for the end of the output Zhangsan, then allow Lisi assignment, in turn.

Input threads need to do the same, waiting after output

Waiting for wake-up mechanism is needed at this time:

Input: After assignment, the execution method wait() waits forever

Output: Wake up and enter notify() after printing, and wait() forever before output.

Input: After waking up, reassigning, notify() must wake up the output thread and wait() for itself.

Cycle down in turn

 

Code implementation:

package demo;

public class Resource {
    public String name;
    public String sex;
    public boolean flag = false;
}
package demo;

public class Input implements Runnable {
    private Resource r;

    public Input(Resource r) {
        this.r = r;
    }

    public void run() {
        int i = 0;
        while (true) {
            synchronized (r) {
                if (r.flag) {
                    try {
                        r.wait();
                    } catch (Exception e) {
                    }
                }
                if (i % 2 == 0) {
                    r.name = "Zhang San";
                    r.sex = "male";
                } else {
                    r.name = "Li Si";
                    r.sex = "female";
                }
                r.flag = true;
                r.notify();
            }
            i++;
        }
    }
}
package demo;

public class Output implements Runnable {
    private Resource r;

    public Output(Resource r) {
        this.r = r;
    }

    public void run() {
        while (true) {
            synchronized (r) {
                if (!r.flag) {
                    try {
                        r.wait();
                    } catch (Exception e) {
                    }
                }
                System.out.println(r.name + "..." + r.sex);
                r.flag = false;
                r.notify();
            }
        }
    }
}
package demo;

public class ThreadDemo {
    public static void main(String[] args) {

        Resource r = new Resource();

        Input in = new Input(r);
        Output out = new Output(r);
        Thread tin = new Thread(in);
        Thread tout = new Thread(out);

        tin.start();
        tout.start();
    }
}

At that time, Zhang San Li interlaced the output.

complete

Tags: Java Database

Posted on Tue, 08 Oct 2019 04:42:41 -0700 by Mark W