java multithreading

2, Multithreading

2.1 creation method of multithreading

1) Inherit Thread class
2) Implement the Runable interface
3) Implement Callable interface
/**
 * callable Method to create a thread
 * Return value
 *
 * Communication between threads (wait/notify)
 */
public class Test1 {

    public static void main(String[] args) {

//        //Start a child thread
//        //Inherit Thread class
//        new Thread(){
//            @Override
//            public void run() {
//
//            }
//        }.start();
//
//        //Inherit the Runable interface
//        new Thread(() -> {
//
//        }).start();

        System.out.println("The main thread starts executing");

        //Open child thread
        FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
        new Thread(futureTask).start();

        //The main thread performs other business processing
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //Gets the return value of the child thread
        try {
            String result = futureTask.get();
            System.out.println("Get the return value of the child thread:" + result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }

}

class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("The child thread begins execution" + Thread.currentThread().getName());
        Thread.sleep(5000);
        System.out.println("End of sub thread execution" + Thread.currentThread().getName());
        return "Hello";
    }
}
4) Thread pool

2.2 thread life cycle

New status - > ready status - > running status < - > blocking status - > dead status

2.3 thread safety issues

2.3. 1 what is thread safety?

In a multithreaded environment, when n threads operate (add, delete and modify) a shared resource at the same time, the inconsistency of resource state may be caused due to the uncertainty of thread scheduling. This problem is thread safety

2.3. 2 how to judge whether there is thread safety in the actual development project?

We should be able to determine whether competitive resources exist and what resources they are

2.3. 3 causes of thread safety

Thread insecurity is caused by:
1. Visibility
2. Atomicity
3. Order
If any business does not guarantee any of the above three features, there will be a thread safety problem

1) Visibility

The modification of a resource by one thread must be immediately visible to other threads

Visibility cases

public class Test2 {

    public static boolean flag = true;

    public static void main(String[] args) {
        System.out.println("The main thread starts executing!");

        //Open child thread
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                flag = false;
                System.out.println("Child thread reset flag Variable:" + flag);
            }
        }.start();


        while(flag){
        }

        System.out.println("End of main thread execution!");
    }
}
2) Atomicity (in the actual development process, the high probability is due to atomicity)

An operation is an integral whole

For example:
i = 10;// Atomicity
i++; // Non atomicity x = I + 1| i=x;
j = 10.0;// Non atomic on 32-bit systems and atomic on 64 bit systems

Note: when two atomic operations are put together, they are not atomic

public class Test3 {

    private static int i = 0;
    
    public static void main(String[] args) {

        for (int j = 0; j < 1000; j++) {
            new Thread(() -> {
                i++;
            }).start();
        }

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("i Results:" + i);
    }
}

3) Order

Instruction rearrangement: cpu may disrupt the execution order of code for optimization.
Guarantee of instruction replay: cpu can ensure that the execution result of the original program will not be affected after instruction replay in the case of single thread.

public class Test3 {

    private static boolean flag = true;
    private static Object obj;

    public static void main(String[] args) {

        //Thread 1
        while(flag){
        }
        int i = obj.hashCode();


        //Thread 2
        obj = new Object();
        flag = false;
    }
}
2.3. 4 how to solve the problem of thread safety
1) Use volatile keyword

volatile keyword can ensure the visibility of variables and local ordering.
volatile cannot guarantee atomicity.

Variables modified with volatile keyword are immediately visible to other threads once modified by one thread.

2) AtomicXxxx class under JUC(java.util.concurrent) package is used to ensure atomicity

The methods in AtomicXxxx class are atomic operations

ublic class Test3 {

    private static int i = 0;
    private static volatile AtomicInteger atomicInteger = new AtomicInteger(i);

    public static void main(String[] args) {

        for (int j = 0; j < 1000; j++) {
            new Thread(() -> {
                atomicInteger.getAndIncrement();//i++
            }).start();
        }

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("i Results:" + atomicInteger.get());
    }
}
3) Use the cas method provided by the AtomicXxxx class

CAS (compare and swap) is essentially an optimistic lock operation

/**
 *  flag.compareAndSet(0, 1)
 *  Judge whether the value of flag is 0. If it is 0, it will be modified to 1 and return true. If it is not 0, it will not be modified and return false
 *  This process is an atomic and indivisible operation
 */
public class Test4 {

    private static String name;//full name
    private static int age;//Age
    private static AtomicInteger flag = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            boolean result = flag.compareAndSet(0, 1);
            if (result) {
                name = "Xiao Hong";
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                age = 18;
            }
        }).start();

        new Thread(() -> {
            boolean result = flag.compareAndSet(0, 1);
            if (result) {
                name = "Xiao Ming";
                try {
                    Thread.sleep(499);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                age = 17;
            }
        }).start();

        new Thread(() -> {
            boolean result = flag.compareAndSet(0, 1);
            if (result) {
                name = "Xiao Gang";
                try {
                    Thread.sleep(498);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                age = 19;
            }
        }).start();

        Thread.sleep(1000);

        System.out.println(name + " " + age);
    }
}
4) Lock with synchronized keyword

synchronized keyword can ensure visibility, atomicity and order
Visibility: when unlocked, all variables will be forcibly refreshed to main memory
Atomicity: after locking, other threads cannot obtain the lock, so they cannot interrupt the program execution in the thread
Order: when atomicity is guaranteed, order is guaranteed

Lock what- important
1. Select smaller grained objects as much as possible
2. this object is locked by default when synchronized modifies a common method
3. When synchronized modifies a static method, it locks the class object of the current class by default

5) Lock with lock object

JDK1.4. Provide a new locking mode, jdk1 After 5, the synchronized keyword is optimized, and the performance is about the same as that of Lock. So there are still many programmers who like to use synchronized keywords

Reentry lock

//Reentry lock 
Lock lock = new ReentrantLock();

new Thread(() -> {
    lock.lock();
    System.out.println("Thread 1 execution");
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("End of thread 1 execution");
    lock.unlock();
}).start();

new Thread(() -> {
    lock.lock();
    System.out.println("Thread 2 execution");
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("End of thread 2 execution");
    lock.unlock();
}).start();

Read write lock

package com.qf.demo14;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test7 {

    public static void main(String[] args) {

        //Read write lock
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        //Read lock
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
        //Write lock
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();


        new Thread(() -> {
            readLock.lock();
            System.out.println("Thread 1 execution");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("End of thread 1 execution");
            readLock.unlock();
        }).start();

        new Thread(() -> {
            writeLock.lock();
            System.out.println("Thread 2 execution");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("End of thread 2 execution");
            writeLock.unlock();
        }).start();
    }
}

Read / write lock:
Read lock compatible read lock
Read lock incompatible with write lock
Write lock incompatible with write lock

6) Use database locks to ensure data consistency (table locks, row locks (shared locks, exclusive locks))
7) Use the Lua script of Redis to ensure data consistency

...

2.3. 5 write a thread safe lazy singleton mode

CAS version

public class Test8 {

    private Test8(){}

    private static AtomicReference<Test8> reference = new AtomicReference<>();

    public static Test8 getInstance(){
        reference.compareAndSet(null, new Test8());
        return reference.get();
    }
    
}

Double lock decision

public class Test8 {

    private Test8(){}

    private volatile static Test8 test8;

    public static Test8 getInstance(){
        if(test8 == null){
            synchronized (Test8.class) {
                if(test8 == null){
                    //Thread 1
                    test8 = new Test8();
                    //Initialize object
                    //1. Request heap memory space
                    //2. Initialize the requested heap memory space
                    //3. Point the variable test8 to the heap memory space
                }
            }
        }
        return test8;
    }
}
2.3. 6 deadlock
Object obj = new Object();
Object obj2 = new Object();

new Thread(() -> {
    System.out.println("Thread 1 executes....");
    synchronized (obj) {

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized (obj2){

        }

    }
    System.out.println("Thread 1 ends....");
}).start();


new Thread(() -> {
    System.out.println("Thread 2 executes....");
    synchronized (obj2) {

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized (obj){

        }

    }
    System.out.println("Thread 2 ends....");
}).start();
2.3. 7 thread safety in Collections
1) ArrayList, HashMap, etc. are thread unsafe collections, so what's the problem with using these thread unsafe collections in multithreading?

When adding elements to these basic collections, because they are multi-threaded and do not have any locking mechanism, there is a high probability that some elements will be overwritten or lost. Multithreading reading and writing at the same time will also cause some problems, such as reading the intermediate state of the writing thread, causing business judgment problems, and so on

2) How to solve the problem of unsafe collection in multi-threaded operation?

In a multithreaded environment, you can use a thread safe collection, such as Vector and Hashtable. However, the fine-grained locks of Vector and Hashtable are too large, and the performance loss of highly concurrent read and write is too high. It is not recommended in the actual development process. Jdk1 is recommended Some thread safe collections provided in the JUC package launched after 4, such as ConcurrentHashMap, ensure thread safety and improve concurrency as much as possible.

reflection:
1. Is it true that ArrayList, a thread unsafe collection, cannot be used in the actual development process- No, specific analysis of specific problems
2. Does the use of thread safe collections mean that thread safety problems will not occur- The so-called thread safe collection only means that each individual method in it is thread safe, but the business formed by combining these methods does not guarantee thread safety

3) CopyOnWriteArrayList - thread safe ArrayList

CopyOnWriteArrayList adopts the method of reentry lock + copy on write to ensure the thread safety of the collection. Lock when adding and no lock when writing (copy when writing), so as to improve the reading efficiency of the collection.
CopyOnWriteArrayList is especially suitable for scenarios with more reads and less writes. There is no locking mechanism for concurrent reads, but the cost of writing will be higher. Each write needs to copy a new array. Moreover, in this way, the read data may have a certain probability that it is old data. Therefore, if the program allows such inconsistency (final consistency) in a short time, CopyOnWriteArrayList is very appropriate. However, if the program must require absolute consistency of data, it should be used at this time

List list = Collections.synchronizedList(new ArrayList<>());

This way you get a thread safe collection

Copy on write

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}
4) ConcurrentHashMap - thread safe version of HashMap

Underlying implementation

JDK1. Before 7, the thread safety was guaranteed by means of segmented lock
Jdk1. After 8, CAS + synchronized is used to ensure thread safety (smaller granularity than JDK1.7 lock)

Source code analysis

Method of adding element (put)

final V putVal(K key, V value, boolean onlyIfAbsent) {
    //key and value cannot be null
    if (key == null || value == null) throw new NullPointerException();
    //Call the hash function to calculate the hash value of the key
    int hash = spread(key.hashCode());
    //1. Flag bit
    //2. Length of linked list
    int binCount = 0;
    //Cycle spin
    //tab points to the underlying hash table
    for (Node<K,V>[] tab = table;;) {
        //f - add the first element of the hash bucket corresponding to the element
        //n - capacity of hash table
        //i - subscript calculated by adding element (position of corresponding hash bucket)
        //fh - flag bit
        Node<K,V> f; int n, i, fh;
        //Determine whether the hash table is initialized
        if (tab == null || (n = tab.length) == 0)
            //The hash table has not been initialized yet. Please perform the initialization operation
            //The initTable method is thread safe and ensures that only one thread can initialize the hash table
            tab = initTable();
        
        //i = (n - 1) & hash calculates the position of the hash bucket of the new element and assigns it to the i variable
        //Tabat (tab, I = (n - 1) & hash) is equivalent to tab [i = (n - 1) & hash]
        //Because the hash table itself guarantees visibility, but the elements in the hash table do not guarantee visibility,
        //Tabat (tab, I = (n - 1) & hash) this method directly obtains elements from memory through memory address (to ensure visibility)
        //In fact, the first element of bucket i is assigned to f from the hash table to judge whether it is null
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            //No hash collision occurred
            //The new value is encapsulated into a Node node and placed in the bucket
            //Judge whether there is an element in the position of bucket i through CAS. If there is no element, assign a value (atomicity). If there is, stop the operation, enter the next round of for cycle and continue to spin
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        //Get the hash value of the first element f at the bucket i position and assign it to fh
        //If fh==-1, it indicates that the bucket i is in the process of capacity expansion migration
        else if ((fh = f.hash) == MOVED)
            //The current thread guarantees the expanded thread to migrate the elements of bucket i
            //After the migration is completed, the latest hash table is returned
            tab = helpTransfer(tab, f);
        else {
            //Indicates that the current bucket i has elements and is not migrating
            //The new element can be put into the bucket i, but the hash collision problem needs to be solved
            V oldVal = null;
            //Lock the first element f of the lock barrel i
            //One bucket and one lock minimize the fine granularity of the lock
            synchronized (f) {
                //Double lock judgment: judge the condition again after locking
                if (tabAt(tab, i) == f) {
                    //FH > 0 indicates that the current bucket i is a linked list
                    if (fh >= 0) {
                        //Because it is a linked list, binCount is set to 1
                        //In the process of linked list circulation, binCount represents the length of the linked list
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            //Determine whether the key is repeated
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                //Put the new element at the end of the linked list
                                //Tail insertion
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    //Judge whether the current bucket i is a red black tree
                    else if (f instanceof TreeBin) {
                        //Logic of popular black tree
                        Node<K,V> p;
                        //If it is a red black tree, binCount will be set to 2
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                //binCount = 1 linked list
                //binCount = 2 red black tree
                if (binCount >= TREEIFY_THRESHOLD)
                    //Determine whether it is necessary to turn into a red black tree
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    //Expansion of hash table
    addCount(1L, binCount);
    return null;
}

Initialize hash table

//Initialize hash table
//There may be n threads executing the method at the same time
private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    //spin
    //If the hash table is empty, go through the loop body. If the hash table is not empty, directly return the hash table object
    while ((tab = table) == null || tab.length == 0) {
        //If SC < 0, it indicates that a thread is already initializing the hash table, and the current thread enters the concession state
        if ((sc = sizeCtl) < 0)
            Thread.yield(); // lost initialization race; just spin
        //By cas, setting sizeCtl to - 1 indicates that a thread has initialized the hash table
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                //Double judgment
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    //Initialize hash table
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    //Assign a hash table to a global variable
                    table = tab = nt;
                    sc = n - (n >>> 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}
2.3. 8 thread pool

Parameter introduction

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

Parameter 1: corePoolSize, the number of core threads, and the minimum number of threads in the thread pool

Parameter 2: maximumPoolSize, the maximum number of threads. If there are not enough threads in the thread pool, a new thread will be created, but it will not exceed this number

Parameter 3: keepAliveTime: the maximum idle time of a thread. Threads that exceed the number of core threads will be automatically recycled after the idle time is exceeded

Parameter 4: unit, unit of idle time

Parameter 5 (important): workQueue, blocking queue object (type) of thread pool

Parameter 6: threadFactory, the factory object created by the thread (determines the creation method of the thread)

Parameter 7: handler, if the redundant task (Runnable) cannot be put into the blocking queue, how to reject the task (4 methods)

2.4 communication between threads

2.4. 1 what is inter thread communication?

Because in the multithreaded environment, thread scheduling is deterministic, but sometimes the business needs one thread to continue based on the results of another thread. At this time, the problem of inter thread communication needs to be considered

2.4. 2. Communication mode between threads

1,wait/notify/notifyAll
2. Blocking queue

2.4.3 Wait/Notify

1. wait and notify must be written in the synchronization code block (synchronization method)
2. There must be a synchronization lock object to call the wait and notify methods

Question:
1. What is the difference between wait and sleep- Wait will release the lock resource, but sleep will not

2.4. 4 blocking queue

add - if the queue is full, an error will be reported when adding an element
put - if the queue is full, adding elements will block
poll - if there is no element, null is returned directly
take - if there is no element, the current thread will be blocked

2.4. 5 actual application scenario of inter thread communication

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-vJm1sTSn-1627987591497)(img/image-20200804160457464.png)]

**Interview question: * * there are ABCD4 threads, thread A can only write A, thread B can only write B, and so on. Please write A program to output 4 files 1,2,3,4 through ABCD4 threads. There is only ABCD in the first file, BCDABCDA in the second file, CDABCDAB in the third file and DABCDABC in the fourth file

Keywords: JavaSE

Added by gergy008 on Fri, 31 Dec 2021 17:42:41 +0200