Cooperation between threads (1) - waiting/notification mechanism

    When using threads to run multiple tasks at the same time, you can use locks (mutual exclusion) to synchronize the behavior of the two tasks, so that one task will not interfere with the other task, that is, the two tasks are alternately using a shared Resource, the use of mutual exclusion can ensure that only one task can access the resource at a time.

    Solve the problem of resource competition caused by multi-threading, so how do we realize the cooperation between threads? Enables multiple tasks to work together to solve a problem, that is, the problem now is not the interference between threads, but how to coordinate between threads, because in such problems, some parts must be solved before other parts are solved. .

    One thread modifies the value of an object, and another thread senses the change and then operates accordingly. The whole process starts in one thread, and the final execution is in another thread . The former is the producer and the latter is the consumer. This mode isolates "what" and "how", achieves decoupling at the functional level, and has good scalability in the architecture , The simple way to implement in the Java language is to let the consumer thread continuously check whether the variable meets the expectations, as shown in the following code, set the unsatisfied conditions in the while loop, and exit the while loop if the conditions are met, thus completing the Consumer work.

while (value != desire) {
   Thread.sleep(1000);
}
doSomething();

    But the above code also brings other problems

  • Difficult to ensure timeliness. During sleep, it basically does not consume processor resources, but if you sleep for too long, you will not be able to find out that the conditions have changed in time, that is, the timeliness is difficult to guarantee.
  • Difficult to reduce overhead. If you reduce the sleep time, for example, sleep for 1 millisecond, so that consumers can detect changes in conditions more quickly, but it may consume more processor resources and cause unwarranted waste.

    Therefore, we need Java's built-in wait/notification mechanism to better solve this problem. The relevant methods to implement the wait/notify mechanism are available to all objects because they are defined on the Object object.

1. Concept

    The waiting/notification mechanism means that a thread A calls the wait() method of the object O to enter the waiting state, while another thread B calls the notify() or notifyAll() method of the object O. O's wait() method returns to perform subsequent operations. The above two threads complete the interaction through the object O, and the relationship between wait() and notify/notifyAll() on the object is like a switch signal, which is used to complete the interaction between the waiting party and the notifying party. (It can also be implemented by await(), signal()/signalAll() of the Condition class)

2. Related APIs 

    1.wait()

    When an object calls this method, the thread will enter the waiting state indefinitely (or waiting to return, that is, return, similar to the accept method of the ServerSocket object in network programming, the method can only end when a client establishes a connection, more directly To put it bluntly, it is equivalent to a while infinite loop inside the wait() method. The condition for the loop to escape is that the object is woken up by other threads. method will return, pay attention! After returning, it does not necessarily enter the running state, but first becomes the ready state, and then continues to run.

//当然,内部实现并不是这样,仅仅作为一个比喻
private boolean token=false;
//线程A调用对象的wait1方法,陷入等待
public void wait1(){
	while(token==false){
		
	}
	return;
}
//线程B调用同一个对象的notify1方法,便可实现唤醒
public void notify1(){
	token=true;
}

    2.wait(long time),wait(long time,int  nanos)

    Timeout waiting, waiting for notification from another thread or returning automatically after waiting a period of time, time is milliseconds, nanos is nanoseconds.

    3.notify()

    Notifies a thread waiting on this object to return from the wait() method, provided that the current thread acquires the lock on the same object.

    4.notifyAll()

    Notifies all threads waiting on this object to return from the wait() method.

3. Detailed explanation

    1) Why are the wait() and notify/notifyAll() methods placed in the Object class instead of the Thread class? These three methods are all thread-oriented but are implemented as general functions of all classes, because waiting and waking up must be is the same lock. The lock can be any object, so the method that can be called by any object is defined in the object class. Since wait() and notify/notifyAll() must operate on locks, wait() and notify/notifyAll() must be in synchronized blocks or methods. (Condition can be used in Lock locks)

public class WaitNotify {
	static boolean flag = true;
	static Object lock = new Object();

	public static void main(String[] args) throws Exception {
		Thread waitThread = new Thread(new Wait(), "WaitThread");
		waitThread.start();
		TimeUnit.SECONDS.sleep(1);
		Thread notifyThread = new Thread(new Notify(), "NotifyThread");
		notifyThread.start();
	}

	static class Wait implements Runnable {
		public void run() {
			// 加锁,拥有lock的Monitor
			synchronized (lock) {
				// 当条件不满足时,继续wait,同时释放了lock的锁
				while (flag) {
					try {
						System.out.println(Thread.currentThread() + " flag is true. wait"
								+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
						lock.wait();
					} catch (InterruptedException e) {
					}
				}
				// 条件满足时,完成工作
				System.out.println(Thread.currentThread() + " flag is false. running"
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
			}
		}
	}

	static class Notify implements Runnable {
		public void run() {
			// 加锁,拥有lock的Monitor
			synchronized (lock) {
				// 获取lock的锁,然后进行通知,通知时不会释放lock的锁,
				// 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
				System.out.println(Thread.currentThread() + " hold lock. notify @ "
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
				lock.notifyAll();
				flag = false;

			}
			// 再次加锁
			synchronized (lock) {
				System.out.println(Thread.currentThread() + " hold lock again. sleep"
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));

			}
		}
	}
}

    2) Details when using wait()/notify():

  • When using wait(), notify() and notifyAll(), you need to lock the calling object first.
  • After calling the wait() method, the thread state changes from RUNNING to WAITING, and the current thread is placed in the object's waiting queue.
  • After the notify() or notifyAll() method is called, the waiting thread still does not return from wait(), and the thread that needs to call notify() or notifyAll() releases the lock before the waiting thread has a chance to return from wait().
  • The notify() method moves a waiting thread in the waiting queue from the waiting queue to the synchronization queue, while the notifyAll() method moves all the threads in the waiting queue to the synchronization queue, and the state of the moved thread changes from WAITING is BLOCKED.
  • The premise of returning from the wait() method is to acquire the lock of the calling object.

    3) Classic paradigm of wait/notify mechanism

        The waiting party follows the following principles:

  • Acquires a lock on an object.
  • If the condition is not met, the object's wait() method is called, and the condition is checked after being notified.
  • If the conditions are met, the corresponding logic is executed.

        The pseudo code is:

synchronized(对象) {
    while(条件不满足) {
        对象.wait();
    }
    对应的处理逻辑
}

        Notifying parties shall follow:

  • Acquires a lock on an object.
  • Change the conditions.
  • Notify all threads waiting on the object.

        The pseudo code is:

synchronized(对象) {
    改变条件
    对象.notifyAll();
}

4. The second implementation method Lock and Condition

    In addition to using wait()/notify() in the Object class, it can also be implemented by combining Lock with Condition. The specific usage is similar to wait()/notify().

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class AwaitAndSignal {
	static boolean flag = true;
	static Lock lock = new ReentrantLock();
	static Condition con=lock.newCondition();
	public static void main(String[] args) throws Exception {
		Thread AwaitThread = new Thread(new Await(), "AwaitThread");
		AwaitThread.start();
		TimeUnit.SECONDS.sleep(1);
		Thread SignalThread = new Thread(new Signal(), "SignalThread");
		SignalThread.start();
	}

	static class Await implements Runnable {
		public void run() {
			try{
				lock.lock();
				while(flag&&!Thread.interrupted()){
					try {
						System.out.println(Thread.currentThread() + " flag is true. wait"
								+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
						con.await();
						
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.out.println(Thread.currentThread() + " flag is false. running"
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
			}finally {
				lock.unlock();
			}
		}
	}

	static class Signal implements Runnable {
		public void run() {
			try{
				lock.lock();
				System.out.println(Thread.currentThread() + " hold lock. notify"
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
				con.signalAll();
				flag = false;
			}finally {
				lock.unlock();
			}
			// 再次加锁
			try{
				lock.lock();
				System.out.println(Thread.currentThread() + " hold lock again. sleep"
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
			}finally {
				lock.unlock();
			}
		}
	}
}

5.Thread.join() method

    If a thread A executes the thread.join() statement, its meaning is: the current thread A waits for the thread thread to terminate before returning from thread.join(). In addition to the join() method, thread Thread also provides two methods with timeout characteristics: join(long millis) and join(longmillis, int nanos).

public class ThreadMethodJoin {
	public static void main(String[] args) throws Exception {
		Thread previous = Thread.currentThread();
		for (int i = 0; i < 10; i++) {
		// 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
		Thread thread = new Thread(new Domino(previous), String.valueOf(i));
		thread.start();
		previous = thread;
		}
		TimeUnit.SECONDS.sleep(5);
		System.out.println(Thread.currentThread().getName() + " terminate.");
	}
	static class Domino implements Runnable {
		private Thread thread;
		public Domino(Thread thread) {
			this.thread = thread;
		}
		public void run() {
			try {
				thread.join();
			} catch (InterruptedException e) {
			}
			System.out.println(Thread.currentThread().getName() + " terminate.");
		}
	}

}

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325467156&siteId=291194637