[Reading Notes] Chapter 2: Java Parallel Fundamentals-"Practical Java High Concurrency Programming"

Chapter 2: Java Parallel Fundamentals

2.1 Processes and threads

  • Process: The basic unit of the system for resource allocation and scheduling;
  • Thread: The smallest unit of program execution.

A process is a container for threads.

2.2 The basic operation of threads

2.2.1 New thread

Thread t1 = new Thread(); //创建线程对象
t1.start();//启动线程

start() will create a new thread and let this thread execute the run() method, so if we need the new thread to execute something, we need to rewrite the run() method.

Example:

Thread t1 = new Thread(){
    
    //使用匿名类重写run()方法
	@Override
	public void run(){
    
    
		System.out.println("Hello,I am t1");
	}
};
t1.start();

A more reasonable and commonly used method to start a new thread: (The following is from "Head First Java")

①Create Runnable object (task of thread)

Runnable threadJob = new MyRunnable();

The MyRunnable class is inherited from the Runnable interface. This interface has only one method run(), which must be overridden.

② Create a Thread object and assign Runnable (task)

Thread myThread = new Thread(threadJob);

③Start Thread

myThread.start();

The start() method will execute the run() method of Runnable (task).

Full example:

public class MyRunnable implements Runnable{
    
    
	public void run(){
    
    
		go();
	}
	public void go(){
    
    
		System.out.println("Hello,I am myThread!");
	}
}
class ThreadTester{
    
    
	public static void main(String [] args){
    
    
		Runnable threadJob = new MyRunnable(); //①
		Thread myThread = new Thread(threadJob); //②
		myThread.start(); //③
	}
}

2.2.2 Terminate the thread

Thread provides a stop() method, which can terminate the thread immediately. But this method and is not recommended , because this method is too violent, forced to perform to half the thread terminates, may cause some data inconsistencies.

The correct way is to write a stopMe() method yourself:

example:

public static class ChangeObjectThread extends Thread {
    
    
    volatile boolean stopme = false;

    public void stopMe() {
    
    
        stopme = true;
    }
    @Override
    public void run() {
    
    
        while (true) {
    
    //一直执行,直到调用stopMe方法
            if (stopme) {
    
    
                System.out.println("exit by stop me");
                break;
            }
            synchronized (u) {
    
    
                int v = (int) (System.currentTimeMillis() / 1000);
                u.setId(v);
                try {
    
    
                    Thread.sleep(100);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                u.setName(String.valueOf(v));
            }
            Thread.yield();//yield()是让出资源
        }
    }
}

2.2.3 Thread interrupt

"Thread interruption does not mean that the thread exits immediately. Instead, it sends a notification to the target thread to inform the target thread that someone wants you to exit! As for how to handle the target thread after being notified, it is entirely up to the target thread to decide."

Three methods related to thread interruption:

public void Thread.interrupt();//中断线程(通知目标线程中断,即设置中断标志位)
public boolean Thread.isInterrupted();//判断是否中断(通过检查中断标志位)
public static boolean Thread.interrupted();//判断是否中断,并清除当前中断状态

example:

public class CreateThread implements Runnable {
    
    
    @Override
    public void run() {
    
    
        while (true) {
    
    
            if (Thread.currentThread().isInterrupted()) {
    
    //如果检测到当前线程被标志为中断状态,则结束执行
                System.out.println("Interrupted");
                break;
            }
            try {
    
    
                Thread.sleep(2000);//休眠2s,当线程休眠中被中断会抛出异常
            } catch (InterruptedException e) {
    
    
                System.out.println("Interrupted When Sleep");
                Thread.currentThread().interrupt();//再次设置终端标记位
            }
            Thread.yield();
        }
    }

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

        Thread t1 = new Thread(new CreateThread());
        t1.start();
        Thread.sleep(1000);//休眠1s
        t1.interrupt();//将t1设置为中断状态,需要在run()中进行中断处理
    }
}

Note: The Thread.sleep() method throws an exception due to interruption. At this time, it will clear the interruption flag. If it is not processed, then the interruption cannot be caught at the beginning of the next cycle, so the interruption flag is set again in the exception handling Bit.

2.2.4 Wait and notify

The wait() method and notify() method are in the Object class, and any object can call these two methods.

When the wait() method is called on an object instance, the object will stop continuing to execute and turn to a waiting state until other threads call the notify() method.

The workflow of wait() and notify():

If a thread calls object.wait(), it will enter the waiting queue of the object object. There may be multiple threads in this waiting queue, because the system is running multiple threads waiting for a certain object (resource) at the same time. When object.notify() is called, it will randomly select a thread from this waiting queue and wake it up.

Illustration: (Note: notifyAll(): wake up all waiting threads in the waiting queue.)
Insert picture description here
Note: The wait() method and notify() method are included in the corresponding synchronized statement, that is, in the call to wait() or notify( ) You need to obtain the object monitor first, and release the object monitor after execution.
Insert picture description here
example:

public class SimpleWN {
    
    
    final static Object object = new Object();
    public static class T1 extends Thread {
    
    
        public void run() {
    
    
            synchronized (object) {
    
    
                System.out.println(System.currentTimeMillis() + ":T1 start!");
                try {
    
    
                    System.out.println(System.currentTimeMillis() + ":T1 wait for object ");
                    object.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ":T1 end! ");
            }
        }
    }

    public static class T2 extends Thread {
    
    
        public void run() {
    
    
            synchronized (object) {
    
    
                System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread ");
                object.notify();
                System.out.println(System.currentTimeMillis() + ":T2 end! ");
                try {
    
    
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
    
    
        Thread t1 = new T1();
        Thread t2 = new T2();
        t1.start();
        t2.start();
    }
}

Operation result:
Insert picture description here
As above, after T2 wakes up T1, T1 cannot be executed immediately, but after T2 releases the object's lock, T1 successfully obtains the object's lock to continue execution.

2.2.5 Suspend and resume processes

The suspended process must wait until the resume() method to continue execution.

This pair of methods is not recommended , because the suspend() method does not release any lock resources while causing the thread to suspend, which may cause a bad situation like a deadlock.

Wait for the thread to end (join) and yield (yield)

public final void join() throws InterruptedException
public final synchronized void join(long millis)throws InterruptedException

join(): Represents wireless waiting, it will block the current thread until the target thread finishes executing.

join(long millis): Gives a maximum waiting time. If the target thread is still executing beyond the given time, the current thread will continue to execute because it can't wait.

example:

public class JoinMain {
    
    
    public volatile static int i = 0;

    public static class AddThread extends Thread {
    
    
        public void run() {
    
    
            for (i = 0; i < 10000000; i++) ;
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        AddThread at = new AddThread();
        at.start();
        at.join();
        System.out.println(i);
    }
}

As above, if join() is not used to wait for AddThread in the main function, then the i obtained is likely to be 0 or a very small number, but after join(), it means that the main thread is willing to wait for the execution of AddThread to complete, and then move forward with AddThread. Go, so when join() returns, AddThread has been executed, so i will always be 10000000.

public static native void yield();

yield(): Make the current thread give up the CPU. (Giving up the CPU does not mean that the current thread is not executing. The current thread will compete for CPU resources after giving up the CPU, but whether it can be allocated again is not necessarily.)

2.3 Volatile and Java Memory Model (JMM)

When you use volatile to declare a variable, you are telling the virtual machine that this variable is very likely to be modified by some programs or threads. In order to ensure that after this variable is modified, all threads within the scope of the application can "see" the change, the virtual machine must use some special methods to ensure the visibility of this variable.

2.4 Management by category: thread group

In a system, if the number of threads is large and the function allocation is relatively clear, threads of the same function can be placed in a thread group.

The use of thread groups:

// 创建线程组
ThreadGroup tg = new ThreadGroup("PrintGroup");
// 使用Thread构造方法指定线程所属的县城组
Thread t = new Thread(tg,new ThreadTest(),"T1");

Complete usage example:

public class ThreadGroupName implements Runnable {
    
    
	public static void main(String[] args) {
    
    
		ThreadGroup tg = new ThreadGroup("PrintGroup");
		Thread t1 = new Thread(tg,new ThreadGroupName(),"T1");
		Thread t2 = new Thread(tg,new ThreadGroupName(),"T2");
		t1.start();
		t2.start();
		System.out.println(tg.activeCount());
		tg.list();
 	}
 	
	@Override
	public void run() {
    
    
		String groupAndName = Thread.currentThread().getThreadGroup().getName()+ "-" + Thread.currentThread().getName();
		while(true) {
    
    
			System.out.println("I am " + groupAndName);
			try {
    
    
				Thread.sleep(3000);
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
		}
	}
}

2.5 Staying in the background: Daemon

A daemon thread is a special thread , just like its name. It is the guardian of the system and silently completes some system services in the background . For example, garbage collection threads and JIT threads can be understood as daemon threads. Corresponding to it is the user thread. The user thread can be considered as the working thread of the system, which will complete the business operations that this program should complete . If the user threads are all finished, it also means that the program actually has nothing to do. The object to be guarded by the daemon thread no longer exists, so the entire application should naturally end. Therefore, when there are only daemon threads in a Java application, the Java virtual machine will naturally exit .

The use of daemon threads:

public class DaemonDemo {
    
    
	public static class DaemonT  extends Thread{
    
    
    	public void run(){
    
    
	       while (true){
    
    
	           System.out.println("I am Alive");
	           try {
    
    
	               Thread.sleep(1000);
	           } catch (InterruptedException e) {
    
    
	               e.printStackTrace();
	           }
	       }
		}
    }
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t=new Daemon();
        t.setDaemon(true);//设置守护线程(该设置一定要在start()之前设置,否则设置无效)
        t.start();
        Thread.sleep(2000);
    }
}

As above, t is set as a daemon thread, and only the main thread main is the user thread in the system, so the whole program will end when the main thread exits after sleeping for 2 seconds. If t is not set as a daemon thread, the t thread will continue to print after the main thread ends, and it will never end.

2.6 Thread priority

Threads in Java can have their own priority. High-priority threads will have an advantage when competing for resources and are more likely to seize resources. Of course, this is only a matter of probability. If you are unlucky, high-priority threads may also fail to preempt.

2.7 The concept of thread safety and the keyword synchronized

Thread safety is the foundation of parallel programs.

The keyword synchronized can be used to ensure thread safety:

The role of the keyword synchronized is to achieve synchronization between threads. Its job is to lock the synchronized code so that only one thread can enter the synchronization block at a time, thus ensuring the security between threads.

The keyword synchronized can be used in multiple ways:

  • Specify the lock object: lock the given object, and obtain the lock of the given object before entering the synchronization code.
  • Acting directly on the instance method: it is equivalent to locking the current instance, and obtaining the lock of the current instance before entering the synchronization code.
  • Acting directly on static methods: it is equivalent to locking the current class, and obtaining the lock of the current class before entering the synchronization code.

In addition to being used for thread synchronization and ensuring thread safety, synchronized can also ensure visibility and order between threads. From the perspective of visibility, synchronized can completely replace the function of volatile, but it is not so convenient to use. In terms of sequence, because synchronized restricts that only one thread can access the synchronized block at a time, no matter how the code in the synchronized block is executed out of order, as long as the serial semantics are consistent, the execution result is always the same. Other access threads must enter the code block to read data after obtaining the lock. Therefore, the final result they see does not depend on the execution process of the code, so the order problem is naturally solved (in other words, synchronized The restricted multiple threads are executed serially).

2.8 Concealed errors in the program

For details, please refer to P61-P69 of "Practical Java High Concurrency Programming".

Guess you like

Origin blog.csdn.net/qq_43424037/article/details/113659484