Java multi-threaded programming

1. Advantages and disadvantages

1.1 Advantages

  • better resource utilization;
  • Programming is simpler in some cases;
  • Resource response is faster;

1.2 Disadvantages

  • The development and design are more complex, involving issues such as multi-threaded access to shared data;
  • The overhead of context switching increases. When the CPU switches from one thread to another thread, it needs to store the current thread's data, counters, etc., and then load the local data of another thread, etc., which is called "context switching";

Second, Java creates multi-threading

2.1 Inheriting the Thread parent class

  • Create an instance of the Thread subclass and override the run method, which will be executed after calling the start( ) method.
public class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println("线程正在运行....");
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
  • In addition, a Thread can be created in the form of an anonymous class
Thread thread1 = new Thread() {
    public void run() {
        System.out.println("线程1正在运行.....");
    }
};
thread1.start();

2.2 Implement the Runnable interface

  • Create a new class instance that implements the java.lang.Runnable interface, and the methods in the instance can be called by threads.
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("MyRunnable正在运行");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}
  • Additionally, it is possible to create an anonymous class that implements the Runnable interface
Runnable runnable1 = new Runnable() {
    public void run() {
        System.out.println("Runnable1 is working");
    }
};
Thread thread1 = new Thread(runnable1);
thread1.start();

3. Thread Safety

  • Running multiple threads in the same program doesn't by itself cause a problem, the problem is that multiple threads access the same resource. Like a memory area (variables, arrays, objects), systems (databases, web services, etc.), files. In fact, these problems can only occur when one or more threads write to these resources, and it is safe for multiple threads to read the same resource as long as the resource has not changed.
  • When multiple threads compete for the same resource, if the access order of the resource is sensitive, there will be a race condition. The code area that causes the race condition to occur is called a critical area.
  • A resource is thread-safe if the creation, use, and destruction of a resource are done within the same thread and never leave the thread's control.

4. Java Synchronized Block

  • Synchronized blocks in Java are marked with synchronized. A synchronized block in Java is synchronized on an object, and all synchronized blocks synchronized on an object can only be entered and performed by one thread at the same time. All other threads waiting to enter the synchronized block will be blocked until the thread executing the synchronized block exits.

There are four ways to implement synchronization blocks:

4.1 Instance Methods

  • Java instance method synchronization is on the object that owns the method. Each instance's method synchronization will be on a different object, the instance to which the method belongs. Only one thread can run in an instance method synchronized block. If multiple instances exist, then one thread can perform operations in one instance synchronized block at a time. One instance one thread.
    public synchronized void add() {
        for(int i=0;i<100;++i) {
            count += 1;
            System.out.println(this.getName() + ":" + count);
        }
    }

4.2 Static method synchronization

  • Synchronization is performed on the class object where the method is located, because a class in the Java virtual machine can only correspond to one class object, so only one thread is allowed to execute the static synchronization method in the same class at the same time.
    public static synchronized void add() {
        for(int i=0;i<100;++i) {
            count += 1;
            System.out.println(":" + count);
        }
    }

4.3 Instance method synchronized block

    public void run() {
        for(int i=0;i<100;++i) {
            synchronized(this){
                count += 1;
                System.out.println(this.getName() + ":" + count);
            }
        }
    }
  • The Java synchronized block constructor encloses objects in parentheses. In the above case, "this" is used, which is the instance itself that calls the add method. Objects enclosed in parentheses in a synchronization constructor are called monitor objects. The above code uses the monitor object to synchronize, and the synchronized instance method uses the instance of the calling method itself as the monitor object. Only one thread at a time can execute within a Java method that is synchronized with the same monitor object.
  • The following two examples are both synchronous on the instance object called, so they are equivalent in the effect of synchronous execution.
public class MyThread {

    public synchronized void log1(String msg1,String msg2){
        System.out.println(msg1);
        System.out.println(msg2);
    }

    public  void log2(String msg1,String msg2){
        synchronized(this) {
            System.out.println(msg1);
            System.out.println(msg2);
        }
    }

}

4.4 Static method synchronized blocks

  • The following two methods do not allow simultaneous access by threads. If the second synchronized block is not synchronized on the MyThread.class object, then these two methods can be accessed by the thread at the same time.
public class MyThread {

    public static synchronized void log1(String msg1,String msg2){
        System.out.println(msg1);
        System.out.println(msg2);
    }

    public static void log2(String msg1,String msg2){
        synchronized(this) {
            System.out.println(msg1);
            System.out.println(msg2);
        }
    }

}

Five, Java thread communication

  • The purpose of thread communication is to enable threads to send signals to each other. And thread communication can wait for signals from other threads.
  • Java has a built-in wait mechanism to allow threads to become non-running while waiting for a signal. The java.lang.Object class defines three methods, wait( ), notify( ), notifyAll( ) to implement this waiting mechanism.
  • Once a thread calls the wait( ) method of any object, it becomes inactive until another thread calls the notify( ) method of the same object. In order to call wait( ) or notify( ), the thread must first acquire the lock on that object. That is, the thread must call wait( ) or notify( ) in a synchronized block.
public class MyWaitNotify {
    private final Object myMonitor=new Object();
    private boolean signaled=false;
    
    public void doWait(){
        synchronized(myMonitor){
            while(!signaled) {
                try {
                    myMonitor.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            signaled=false;
        }
    }
    
    public void doNotify(){
        synchronized(myMonitor){
            signaled=true;
            myMonitor.notify();
        }
    }
}

From the above case it can be inferred that:

  1. Both wait( ) and notify( ) are called in a synchronized block by either the waiting thread or the awakening thread. It's mandatory! A thread cannot call wait( ), notify( ) or notifyAll( ) if it does not hold the object lock. Otherwise, an IllegalMonitorStateException will be thrown;
  2. Once a thread calls the wait( ) method, the lock held on the monitor object is released, allowing other threads to call wait( ) or notify( );
  3. In order to avoid losing signals, they must be stored in the signal class, such as the signalled variable above;
  4. Spurious wakeups: For inexplicable reasons, threads may wake up without calling notify( ) and notifyAll( ), which are called spurious wakeups. In order to prevent false wakeups, the member variable holding the signal will be checked in a while loop, not in an if expression, such a while loop is called a spinlock;
  5. Do not call wait( ) on a string constant or a global object, that is, myMonitor above cannot be a string constant or a global object. Instead of calling wait( ) or notify( ) on an empty string, each instance of MyWaitNotify has its own listener object.

Six, Java lock

Java comes with some locks and does not require programmers to implement them.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

A simple implementation of a reentrant lock ,

public class MyLock {
    private boolean isLocked=false;
    private Thread lockedBy;
    private int lockedCount;
    
    public synchronized void lock() throws InterruptedException {
        Thread callingThread=Thread.currentThread();
        while(isLocked&&lockedBy!=callingThread){
            wait();
        }
        isLocked=true;
        lockedCount++;
        lockedBy=callingThread;
    }
    
    public synchronized void unlock() {
        if(Thread.currentThread()==this.lockedBy){
            lockedCount--;
            if(lockedCount==0){
                isLocked=false;
                notify();
            }
        }
    }
}

Remember to release the lock unlock( )

lock.lock( );
try{
    work();
) finally {
    lock.unlock( );
}

Seven, other synchronization methods in Java

  • Signal amount (semaphore): import java.util.concurrent.Semaphore
  • Blocking Queue: import java.util.concurrent.BlockingQueue
import java.util.LinkedList;
import java.util.List;

public class BlockingQueue {
    private List queue=new LinkedList<>();
    private int limit=0;
    
    public BlockingQueue(int limit){
        this.limit=limit;
    }
    
    public synchronized void enqueue(Object obj) throws InterruptedException {
        while(queue.size()==limit){
            wait();
        }
        if(queue.size()==0){
            notifyAll(); //唤醒所有线程
        }
        queue.add(obj);
    }
    
    public synchronized Object dequeue() throws InterruptedException {
        while(queue.size()==0){
            wait();
        }
        if(queue.size()==limit){
            notifyAll();
        }
        return queue.remove(0);
    }
}

Eight, Java thread pool

Java provides four thread pools through Executors, namely:

  • newCachedThreadPool: Creates a cacheable thread pool. If the size of the thread pool exceeds the number of threads required to process tasks, some idle (60 seconds of no task execution) threads will be recycled. When the number of tasks increases, this thread pool can add new threads to process tasks. This thread pool does not limit the size of the thread pool, and the size of the thread pool completely depends on the maximum thread size that the operating system (JVM) can create;
  • newFixedThreadPool: Creates a fixed-size thread pool. Each time a task is submitted, a thread is created until the thread reaches the maximum size of the thread pool. Once the size of the thread pool reaches the maximum value, it will remain unchanged. If a thread ends due to an abnormal execution, the thread pool will add a new thread;
  • newScheduledThreadPool: Create a thread pool with unlimited size, this thread pool supports timing and periodic execution of tasks;
  • newSingleThreadExecutor: Creates a single-threaded thread pool. This thread pool supports timing and periodic execution of tasks. Only one thread is working in this thread pool, which is equivalent to executing all tasks serially by a single thread. If this unique thread ends abnormally, a new thread will replace it. This thread pool ensures that all tasks are executed in the order in which they were submitted.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyPool {
    public static void main(String[] args){
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for(int i=0;i<10;++i){
            final int idx=i;
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(idx);
                }
            });
        }
    }
}

Guess you like

Origin blog.csdn.net/Jiangtagong/article/details/123643150