High concurrency processing experience under Java multithreading

Threads in Java: in Java, each thread has a call stack stored in the thread stack, A Java application always starts from main () function starts running, which is called the main thread. Once a new thread is created, a thread stack will be generated. Threads are generally divided into user threads and guard threads. When all user threads are executed, the JVM will automatically close. However, the waiting thread is not independent of the JVM. The waiting thread is generally created by the operating system or the user.

Thread life cycle: when a thread is created and enters the new state, the JVM allocates memory space and initializes it. When the thread object calls the start() method, the thread is in the ready state (executable state), and the JVM will create a method call stack, and program counter for it. The thread in the executable state can be scheduled and executed by the CPU at any time. When the CPU executes the thread, the thread enters the execution state. During execution, the thread encounters wait() waiting for blocking And synchronized lock synchronization blocking or calling the thread's sleep() method to enter a blocking state. After blocking, wake up through the notify() or notifyall() method to re obtain the object lock, and then enter the ready state. Wait for the CPU to execute in the execution state. When the thread finishes executing or returns, the thread ends normally. If a processing runtime exception occurs, The thread ends because of an exception. This is the life cycle of the entire run of a thread. As shown in the figure below:

Several implementations of threads:

1. Inherit the thread class and override the run method of the class

class MyThread extends Thread {
    
    private int i = 0;

    @Override
    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class ThreadTest {

    public static void main(String[] args) {
        Thread myThread1 = new MyThread(); // 创建一个新的线程  myThread1  此线程进入新建状态
        myThread1 .start();  // 调用start()方法使得线程进入可执行状态
    }
}

2. Implement the runnable interface and rewrite the run () method of the interface. The run () method is also the thread executor. Create an instance of the runnable implementation class and use the instance as the target of the thread class to create the thread object. The thread object is the real thread object

class MyRunnable implements Runnable {
    private int i = 0;

    @Override
    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class ThreadTest {

    public static void main(String[] args) {
        Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象

        Thread thread1 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
               
        thread1.start(); // 调用start()方法使得线程进入就绪状态
              
        }
    }
}

3. Create a thread using the callable and future interfaces. Specifically, create the implementation class of the callable interface and implement the call () method. Futuretask class is used to wrap the object of callable implementation class, and futuretask object is used as the target of thread object to create thread

public class ThreadTest {

    public static void main(String[] args) {

        Callable<Integer> myCallable = new MyCallable();    // 创建MyCallable对象
        FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread thread = new Thread(ft);   //FutureTask对象作为Thread对象的target创建新的线程
                thread.start();                      //线程进入到就绪状态
            }
        }

        System.out.println("主线程for循环执行完毕..");
        
        try {
            int sum = ft.get();            //取得新创建的新线程中的call()方法返回的结果
            System.out.println("sum = " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}


class MyCallable implements Callable<Integer> {
    private int i = 0;

    // 与run()方法不同的是,call()方法具有返回值
    @Override
    public Integer call() {
        int sum = 0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            sum += i;
        }
        return sum;
    }

}

The difference between inheriting thread and implementing runnable interface to implement multithreading:

Inherit the thread class and implement the runnable interface. In program development, as long as it is multithreaded, it must always focus on implementing the runnable interface, because implementing the runnable interface has the following advantages over inheriting the thread class:

1. It can avoid the limitations caused by the single inheritance feature of Java;

2. Enhance the robustness of the program, the code can be shared by multiple threads, and the code and data are independent;

3. It is suitable for multiple thread areas of the same program code to process the same resource.

Thread priority:

Java threads can use setpriority () method sets a priority for the thread. The thread with high priority has a higher probability of being executed first than the thread with low priority. The priority can be expressed as an integer from 0 to 10. 0 is the lowest priority and 10 is the highest priority. When the thread scheduler decides that the thread needs scheduling, it will schedule and select according to this priority; 1) The thread class has three priority static constants: max_ Priority is 10, which is the highest priority of the thread; MIN_ The priority value is 1, which is the lowest priority of the thread; NORM_ The priority value is 5, which is the priority of the middle position of the thread. By default, the priority of threads is norm_ PRIORITY。 2) Generally speaking, threads with high priority first obtain CPU resources and run first. However, in special cases, because the current calculators are configured with multi-core and multi-threaded, threads with low priority may execute first. The specific execution depends on the JVM scheduling.

Several methods of thread synchronization:

1. Using synchronized to obtain object mutex: this method is the most commonly used and relatively safe method. The synchronization mechanism implemented by synchronized modifier is called mutex mechanism, and the lock it obtains is called mutex. Each object has a monitor (lock tag). Only when the thread has the lock tag can it access the resource. If there is no lock tag, it will enter the lock pool. Any object system will create a mutex lock for it. This lock is assigned to the thread to prevent interrupting atomic operations. The lock of each object can only be assigned to one thread, so it is called mutex. When we use synchronization, we need to control the granularity of the lock more carefully. Sometimes it is not necessary to lock the whole method. We only need to lock one code block to meet our business requirements, so as to avoid the impact on performance caused by too long blocking time of other threads.

package per.thread;
 
import java.io.IOException;
 
public class Test {
    
    private int i = 0;
    private Object object = new Object();
     
    public static void main(String[] args) throws IOException  {
        
    	Test test = new test();
        
        Test.MyThread thread1 = test.new MyThread();
        Test.MyThread thread2 = test.new MyThread();
        thread1.start();
        thread2.start();
    } 
     
     
    class MyThread extends Thread{
        @Override
        public void run() {
            synchronized (object) {
                i++;
                System.out.println("i:"+i);
                try {
                    System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");
                    Thread.currentThread().sleep(10000);
                } catch (InterruptedException e) {
                    // TODO: handle exception
                }
                System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束");
                i++;
                System.out.println("i:"+i);
            }
        }
    }
}

2. Thread synchronization is realized by using the special domain variable volatile: the variable modified by volatile is a weak synchronization mechanism, because the member variable in each thread will make a private copy of the object, and the data obtained by each thread is obtained from the private copy memory, while the variable modified by volatile represents that this variable can only be obtained from the shared memory, Private copies are prohibited. When accessing the volatile variable, the locking operation will not be performed, so the execution thread will not be blocked. Therefore, the volatile variable is a lighter synchronization mechanism than the synchronized keyword. From the perspective of memory visibility, writing volatile variables is equivalent to exiting the synchronization code block, and reading volatile variables is equivalent to entering the synchronization code block. However, the code relies too much on volatile variables to control the synchronization state, which is often more unsafe than using locks, and it will be safer to use the synchronization mechanism. Volatile variables should be used only when all of the following conditions are met: 1. The write operation to the variable does not depend on the current value of the variable, or you can ensure that only a single thread updates the value of the variable. 2. The variable is not contained in an invariant with other variables.

 class Bank {
            //需要同步的变量加上volatile
            private volatile int account = 100;
 
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                account += money;
            }
        }

3. Thread synchronization using reentry lock: in jdk1 After 5, Java util. concurrent. This method is provided under the locks package to realize synchronous access. Because there will be a blocking process after synchronized synchronization. If the blocking time is too long, it will seriously affect the quality of our code and bring system performance problems. Because we need a mechanism to enable the waiting thread to respond to interrupts after a certain time. This is the role of lock. In addition, lock can also know whether the thread has successfully obtained the object lock, which synchronized cannot do. Lock provides more functionality than synchronized. However, it should be noted that: 1) lock is not built-in in the Java language. Synchronized is the keyword of the Java language, so it is a built-in feature. Lock is a class through which synchronous access can be realized; 2) There is a big difference between lock and synchronized. Using synchronized does not require the user to manually release the lock. When the synchronized method or synchronized code block is executed, the system will automatically let the thread release the occupation of the lock; Lock requires the user to release the lock manually. If the lock is not released actively, it may lead to deadlock.

package com.dylan.thread.ch2.c04.task;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.reentrantlock;
 
/**
 * This class simulates a print queue
 *
 */
public class PrintQueue {
 
	/**
	 *  创建一个reentrantlock实例 
	 */
	private final Lock queueLock=new reentrantlock();
	
	/**
	 * Method that prints a document
	 * @param document document to print
	 */
	public void printJob(Object document){
        //获得锁 
		queueLock.lock();
		
		try {
			Long duration=(long)(Math.random()*10000);
			System.out.printf("%s: PrintQueue: Printing a Job during %d secondsn",Thread.currentThread().getName(),(duration/1000));
			Thread.sleep(duration);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
            //释放锁 
			queueLock.unlock();
		}
	}
}

Note: selection of lock object and synchronized keyword:

a. You'd better not use either and use a Java util. The concurrent package provides a mechanism to help users deal with all lock related code. b. If the synchronized keyword can meet the needs of users, use synchronized because it can simplify the code. C. if more advanced functions are required, use the reentrantlock class. At this time, pay attention to releasing the lock in time, otherwise deadlock will occur. Usually, release the lock in the finally code

4. Use ThreadLocal to manage variables: if ThreadLocal is used to manage variables, each thread using the variable will obtain a copy of the variable. The copies are independent of each other, so that each thread can modify its own copy of the variable at will without affecting other threads; Common methods of ThreadLocal class:

ThreadLocal() : 创建一个线程本地变量 
get() : 返回此线程局部变量的当前线程副本中的值 
initialValue() : 返回此线程局部变量的当前线程的"初始值" 
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>