[multithreading] Java multithreading learning

Posted by j4IzbInao on Sun, 30 Jan 2022 09:26:34 +0100

1, Thinking in Java

In learning Thinking in Java, I first learned about multithreading. During the learning process, I summarized some useful knowledge and made a note here.

Introduction: sequential and parallel tasks

Most of our programming problems can be solved through sequence problems. However, for some problems, if parallel execution is adopted, the processing speed will be greatly improved. When to use parallel programming? For example, a segment of a program is blocked (usually due to IO blocking or remote request blocking). When considering blocking, can the program continue to execute? Which parts can be executed? This is parallel execution.

Must it be fast?

However, task parallelism may not necessarily improve program processing speed. On a single processor, a single processor has only a single CPU. In the process of parallel execution of tasks, it is necessary to switch tasks and save the execution state of the current task. This part of the time spent is context switching. To sum up, if the task is not blocked and the machine is single processing, using concurrency has no advantage.

Operating system level concurrency

The concurrent processing problem used by the operating system is the most ideal and safe, because the operation on the operating system runs in its own address space, and the tasks will not interfere with each other. At the same time, the operating system will periodically switch the CPU from one process to another.
Some programming languages are designed to isolate tasks from each other concurrently. These languages are called functional languages. The call of each function will not interfere with other functions. Erlang is such a language. At the same time, it also contains a security mechanism for the communication between tasks.
Java is a traditional sequential language, which provides thread support on the basis of sequential language. In Java, threads represent tasks in a single process.

Preemptive and cooperative

Preemptive: it means that the scheduling opportunity interrupts the thread periodically and switches the context to another thread, so that each thread can get a time slice to perform its tasks. Passive comity
Collaborative: each task will automatically give up control of the processor, which requires the programmer to consciously insert some concession statement in each task. An active comity.

Java threading mechanism

The essence of multithreaded programming is to divide a whole program into multiple separate tasks that can run independently. Then each task will have a separate execution thread to complete. A thread is a single sequential control flow in a process. Therefore, a single process can have multiple threads, and each thread seems to have its own CPU, but in fact, the scheduler periodically allocates the execution time of the CPU to each thread.
Therefore, the key task in programming now is how to divide the overall requirements into separate small tasks.

Task definition

// Defining fragment tasks in a program
public class LiftOff implement Runable{
	protected int countDown = 10;
	public void run(){
		while(countDown--> 0){
			System.out.print(countDown);
			Thread.yield();
		}
	}
}
	// Give the task to the thread. Here, run() is not driven by a separate thread, but directly called by the thread to which main() belongs.
	public class MainThread{
		public static void main(String[] args){
			new LiftOff().run();
		}
	}
//Drive tasks through Thread
public class BasicThreads{
	public static void main(String[] args){
		Thread t = new Thread(new LiftOff());
		// Drive tasks through start
		t.start();
	}
}

Define tasks through Executor

Java. For Java SE5 util. The Executor in the concurrent package can manage Thread objects
The Executor executor provides an intermediate layer ExecutorService between the client and task execution, which uses execute() to execute tasks.
ExecutorService is obtained through static methods.


Source code:

	//The Executor in the source code also creates threads through ThreadPoolExecutor
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

newCachedThreadPool and newFixedThreadPool

//Through the Executor class
public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            exec.execute(new LiftOff());
        }
        exec.shutdown();
        //shutdown: closes the ExecutorService object. Prevent new tasks from being submitted to this ExecutorService
    }
}

Callable

For network requests, the task that requires a certain return result is to implement the Callable generic interface, which is the return type. Submit the interface to the submit() of the Executor.
The submit() method returns a Future object. Judge whether the current task is completed by isDone() of the Future object. The get() method will block until the result is returned.

public class TaskWithResult implements Callable<String> {
    private  int  id ;

    public TaskWithResult(int id) {
        this.id = id;
    }

    @Override
    public String call() throws Exception {
        return "result of TaskWithResult " + id;
    }
}
public class CallableDemo {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        ArrayList<Future<String>> results = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            results.add(exec.submit(new TaskWithResult(i)));
        }
        for (Future<String> fs : results) {
            try {
                System.out.println("isDone: " + fs.isDone());
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } finally {
                exec.shutdown();
            }
        }
    }
}

thread priority

Thread priority: the priority of the thread will be passed to the scheduler, which will tend to let the thread with the highest priority execute first. JDK has 10 priorities, but it does not map well with the priority of the operating system.

Thread.yield()

Call thread The thread of yield () gives a hint to the scheduler: my work is almost done, and I can let other threads use the CPU. This is just a hint that the scheduler may not necessarily adopt.

Background thread, daemon thread

Daemon thread: also known as background thread. A general service provided in the background during program operation. If all non background processes end, the program terminates.
Before the thread starts, the setDaemon () method is called to set the current thread as a background thread. And all child threads of the background thread are background threads.
The of the background process will not execute the finally block in try catch finally, and its run() will be aborted.

public class DaemonThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }
}

Thread factory: MyThreadFactory

Define a custom name for the thread, and the online tracking problem log is easy to track.

public class MyThreadFactory implements ThreadFactory {

    /**
     * Atomic operations ensure that each thread has a unique
     */
    private static final AtomicInteger threadNumber = new AtomicInteger(1);

    private final AtomicInteger mThreadNum = new AtomicInteger(1);

    private final String prefix;

    private final boolean daemoThread;

    private final ThreadGroup threadGroup;

    public MyThreadFactory() {
        this("rpcserver-threadpool-" + threadNumber.getAndIncrement(), false);
    }

    public MyThreadFactory(String prefix) {
        this(prefix, false);
    }


    public MyThreadFactory(String prefix, boolean daemo) {
        this.prefix = StringUtils.isNotEmpty(prefix) ? prefix + "-thread-" : "";
        daemoThread = daemo;
        SecurityManager s = System.getSecurityManager();
        threadGroup = (s == null) ? Thread.currentThread().getThreadGroup() : s.getThreadGroup();
    }

    @Override
    public Thread newThread(Runnable runnable) {
        String name = prefix + mThreadNum.getAndIncrement();
        Thread ret = new Thread(threadGroup, runnable, name, 0);
        ret.setDaemon(daemoThread);
        System.out.println("Formed a thread : " + mThreadNum);
        return ret;
    }
}
// Ten threads are started to execute the task
        ExecutorService threadPool = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(resultRow.size() * 2), new MyThreadFactory("invoicemanage"));

join()

a.join() means that a thread cuts a queue. During the execution of the main thread, a.join() is called. Indicates that the main thread will continue to execute downward after the execution of thread a.

public class ThreadJoin {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                testJoin();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                testJoin();
            }
        }, "t2");
        t1.start();
        t2.start();
        try {
            //t1.join();
            //t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("End of main thread");
    }

    public static void testJoin() {
        System.out.println(Thread.currentThread().getName() + "Jump in line");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "end");
    }
}

You can see that t1 and t2 are not finished after the main thread ends

Set T1 join(); And T2 join(); Uncomment and output the following results:

Thread exception capture

Exceptions cannot be propagated across threads. Once the exception escapes from the run() method of the task and propagates to the console, it is not easy to get it.

public class ExceptionThread implements Runnable {
    @Override
    public void run() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        ExecutorService executorService
                = Executors.newCachedThreadPool();
        executorService.execute(new ExceptionThread());
    }
}

How to catch exceptions in threads? I can generate threads through the Executor. ThreadFactory. By giving setUncaughtExceptionHandler() to ThreadFactory; Set the custom interface to catch exceptions. In this way, each created Thread will be attached with a Thread UncaughtExceptionHandler.

class ExceptionThread2 implements Runnable {
    @Override
    public void run() {
        Thread t = Thread.currentThread();
        System.out.println("run() by" + t);
        System.out.println("eh = " + t.getUncaughtExceptionHandler());
        throw new RuntimeException();
    }
}

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println(" caught " + e);
    }
}

class HandlerThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        System.out.println(this + " creating new Thread");
        Thread t = new Thread(r);
        System.out.println("created " + r);
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        System.out.println("eh = " + t.getUncaughtExceptionHandler());
        return t;
    }
}
public class CaptureUncaughtException {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory());
        exec.execute(new ExceptionThread2());
    }
}

Sharing restricted resources and thread safety

Thread safety problem: it refers to that other threads read uncommitted data, that is, dirty reading!
If there are no shared resources between tasks of a single thread, there will be no thread safety problem.
For concurrency problems with competition for shared resources, serialization is used to access shared resources, that is, two threads cannot modify the same resource at the same time. In order to implement this scheme, Java can be completed through synchronized keyword modification methods or static code blocks.
If more than one thread is processing a shared variable, all access or modification methods must be synchronized with synchronized.
A task can obtain the lock of an object multiple times. If a method calls the second locking method on the same object, the lock count will be + 1 Only when the lock count is zero can other threads continue to use this resource.
Safe thread operation:
1. The copy and return values are atomic, there is no possibility of interruption, and there is no intermediate state. However, in order to ensure the visibility between different threads, variables should be declared as volatile
2,
Unsafe thread operation:
1. Increment of value

Atomicity and volatile

1. Java is not an atomic increment operation, which involves increment and write. volatile tells the compiler not to perform any read and write optimizations.
2. If a domain is declared volatile, all read operations can see the modification as long as a write operation occurs in the domain. Even if the local cache is used, the violate field will be flushed to main memory immediately, and the read operation will take place in main memory.

3. On 32-bit machines, there are two separate 32-bit operations when reading and writing shared variables of long and double types, which leads to context switching between reading and writing operations, so different tasks may see the wrong results. volatile should be added to provide more atomicity.

Summary points of synchronized and volatile:
synchronized is added to the method or code fragment and volatile is added to the domain. Do they have to be added to the code
If a domain has a single thread operation, you do not need to set volatile and the method does not need to be synchronized.
If a domain can be operated by multiple threads (read and write), you need to add synchronized methods to read and write to the domain. Adding on the reading method is to prevent dirty reading. Adding on the writing method is to prevent multiple threads from writing to them at the same time, and the written values overlap each other.

If a domain has multiple threads

Here is an example:
The generator in EvenGenerator is a shared resource. The values currentEvenValue and isCancel in this resource are the flags to judge whether the program ends. It is a boolean type, so its assignment is atomic. Theoretically, when val is an odd number, it means that the program execution fails and the program ends. However, in my code, there are two currentevenvalues that increase continuously, that is, they increase with the even arithmetic sequence, and the odd number will not be read outside the function. Let's look at the implementation results:

package chapter21.c21p3;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class EvenChecker implements Runnable {
    private IntGenerator generator;
    private final int id;

    public EvenChecker(IntGenerator generator, int id) {
        this.generator = generator;
        this.id = id;
    }

    @Override
    public void run() {
        while (!generator.isCanceled()) {
            int val = generator.next();
            if (val % 2 != 0) {

                System.out.println(Thread.currentThread().getName() + val + " not even!,generator.isCanceled():" + generator.isCanceled());
                generator.cancel();
                System.out.println(Thread.currentThread().getName() + val + " generator.isCanceled():" + generator.isCanceled()+ " It should stop!");
            }
        }

    }

    public static void test(IntGenerator gp, int count) {
        System.out.println("Press Control-C to Exit");
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < count; i++) {
            exec.execute(new EvenChecker(gp, i));
        }
        exec.shutdown();
    }

    public static void test(IntGenerator gp) {
        test(gp, 10);
    }
}


public class EvenGenerator extends IntGenerator {
    private int currentEvenValue = 0;

    @Override
    public int next() {
        ++currentEvenValue;
        ++currentEvenValue;
        return currentEvenValue;
    }

    public static void main(String[] args) {
        EvenChecker.test(new EvenGenerator());
    }
}
public abstract class IntGenerator {
    private volatile boolean canceled = false;

    public abstract int next();
    public void cancel() {
        canceled = true;
    }

    public boolean isCanceled() {
        return canceled;
    }
}

Execution result:
Theoretically, when the output is not even! Indicates a program error, but it is output many times before it is aborted. Indicates that there is a thread safety problem. How

The mistake is:
1. Currently, it is a multi-threaded environment. All threads of the method modified by currentEvenValue can access it at the same time, resulting in dirty reading
2. The auto increment method of currentEvenValue is not atomic. After the current thread is added, other threads may read the auto increment value of another thread.

Amendment 1:
It will be added because multiple threads can call the next () method at the same time. If the synchronized keyword is added to the method, only one thread can modify the shared variable at the same time, which eliminates the potential competition conditions.

public class SynchronizedEvenGenerator extends IntGenerator {
    private int currentEvenValue = 0;

    @Override
    public synchronized int next() {
        ++currentEvenValue;
        //Thread.yield();
        ++currentEvenValue;
        return currentEvenValue;
    }

    public static void main(String[] args) {
        EvenChecker.test(new SynchronizedEvenGenerator());
    }
}

The results show that the program is successfully executed:

Amendment II:
Lock is used to lock the shared resources. The self increment of the program is that only one thread can obtain the lock.

public class MuteEvenGenerator extends IntGenerator {
    private int currentEvenValue = 0;
    private Lock lock = new ReentrantLock();

    @Override
    public int next() {
        lock.lock();
        try {
            ++currentEvenValue;
            Thread.yield();
            ++currentEvenValue;
            return currentEvenValue;
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        EvenChecker.test(new MuteEvenGenerator());
    }
}

Amendment 3:

Adopt atomic class AtomicInteger,AtomicLong,AtomicReference Special atomic class, increase is atomic
public class IntGeneratorAtomic extends IntGenerator {
    private AtomicInteger atomicInteger = new AtomicInteger(0);
    private Lock lock = new ReentrantLock();

    @Override
    public int next() {
            return  atomicInteger.addAndGet(2);
    }
    public static void main(String[] args) {
        EvenChecker.test(new IntGeneratorAtomic ());
    }
}

Wrong modification scheme 4:

public class IntGeneratorAtomicTwo extends IntGenerator {
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    @Override
    public int next() {
        atomicInteger.addAndGet(1);
        return atomicInteger.addAndGet(1);
    }
    public static void main(String[] args) {
        EvenChecker.test(new IntGeneratorAtomicTwo());
    }
}

Thread abort

Four states of threads:

New: when a thread is created, it will be in this state for a short time. At this time, the necessary thread resources have been allocated. At this time, the thread is qualified to obtain CPU time, and then the state of the thread will become runnable or blocked.
Ready: in this state, the thread can run as long as the scheduler allocates time fragments to the thread.
Blocking: a thread can run, but a condition prevents it from running.
Dead: threads that are dead will no longer be schedulable.

Enter blocking state:

1. Call sleep to put the task into sleep.
2. Call wait() to suspend the thread. Until you get notify or notifyAll, or sign() or signAll().
3. Failed to get synchronized lock.

Thread interrupt

The Thread() class contains an interrupt () method that can interrupt a blocked thread.
Executor () also encapsulates interrupt operations on threads. By calling shutDownNow (), it will send an interrupt() call to all threads it starts.
Cancel () can be called on the Future object to cancel the current task.
For the thread of IO thread, you can close the resources on it to close the thread

Check interrupt

Thread.interrupted() can check whether the thread has been interrupted

Topics: Java Back-end