【Java并发】Daemon、Interrupt、Wait/Notify、Join、ThreadLocal、Pipe

版权声明:转载请注明出处: https://blog.csdn.net/qq_21687635/article/details/84586776

Daemon、Interrupt、Wait/Notify、Join、ThreadLocal、Pipe

关于线程

现代操作系统在运行一个程序时,会为其创建一个进程。现代操作系统调度的最小单元是线程,也叫轻量级进程。

在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等,并且能够访问共享的内存变量。

Daemon

daemon线程是一种支持型线程,当一个Java虚拟机不存在非daemon线程的时候,Java虚拟机就会退出。

一个生动的例子

public class DaemonExample {
	public static void main(String[] args) {
		Thread thread = new Thread(new DaemonRunner(), "DaemonRunner");
		thread.setDaemon(true);
		thread.start();
	}

	static class DaemonRunner implements Runnable {
		@Override
		public void run() {
			try {
				TimeUnit.SECONDS.sleep(10);
			} catch (Exception e) {

			} finally {
				System.out.println("DaemonThread finally run.");
			}
		}
	}
}

运行Daemon程序,可以看到终端没有任何输出。

Interrupt

中断可以理解为线程的一个标识位属性,它表示一个运行中的程序是否被其他程序进行了中断操作。中断好比其他线程对该线程打了个招呼。其他线程通过调用该线程的interrupt()方法对其进行中断操作。

线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。

如果该线程处于终结状态,即使该线程被中断过,调用该线程对象的isInterrupted()时依旧会返回flase。

许多声明抛出InterruptedException的方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,此时调用isInterrupted()方法将会放回false。

一个生动的例子

public class InterruptExample {

	public static void main(String[] args) throws InterruptedException {
		Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
		sleepThread.setDaemon(true);

		Thread busyThread = new Thread(new BusyRnner(), "BusyThread");
		busyThread.setDaemon(true);

		sleepThread.start();
		busyThread.start();

		TimeUnit.SECONDS.sleep(5);

		sleepThread.interrupt();
		busyThread.interrupt();

		System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());

		SleepUtils.second(30);
	}

	static class SleepRunner implements Runnable {
		@Override
		public void run() {
			while (true) {
				System.out.println(Thread.currentThread() + " sleep...");
				try {
					TimeUnit.SECONDS.sleep(10);
				} catch (InterruptedException e) {
					System.out.println("SleepThread interrupted is " + Thread.currentThread().isInterrupted());
					System.out.println(Thread.currentThread() + " interrupted.");
				}
			}
		}
	}

	static class BusyRnner implements Runnable {
		@Override
		public void run() {
			while (true) {
			}
		}
	}
}

可以看到,抛出InterruptException的线程SleepThread,其中断标志位为false,而一直忙碌运作的线程BusyThread,中断标识位为true。

Wait/Notify

相关方法如下:

方法名称 描述
notify() 通知一个在对象上等待的线程,试其从wait()方法放回
notifyAll() 通知所有等待在该对象上的线程
wait() 进入WAITING状态,只有等待另外线程的通知或被中断才会放回
wait(long) 超时等待一段时间,如果没有通知就返回
wait(long, int) 对超时时间更细粒度的控制,可以达到纳秒

一个线程A调用了对象O的wait方法进入等待状态,而另一个线程B调用了对象O的notify()或notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,继续执行后续操作。

一个生动的例子

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

	public static void main(String[] args) throws InterruptedException {
		Thread waitThread = new Thread(new Wait(), "WaitThread");
		waitThread.start();

		TimeUnit.SECONDS.sleep(2);

		Thread notifyThread = new Thread(new Notify(), "NotifyThread");
		notifyThread.start();
	}

	static class Wait implements Runnable {
		@Override
		public void run() {
			synchronized (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 flase. running @ "
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
			}
		}
	}

	static class Notify implements Runnable {
		@Override
		public void run() {
			synchronized (lock) {
				System.out.println(Thread.currentThread() + " hold lock. notify @ "
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
				lock.notifyAll();
				flag = false;
				try {
					TimeUnit.SECONDS.sleep(5);
				} catch (InterruptedException e) {
				}
			}

			synchronized (lock) {
				System.out.println(Thread.currentThread() + " hold lock again. sleep @ "
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
				try {
					TimeUnit.SECONDS.sleep(5);
				} catch (InterruptedException e) {
				}
			}
		}
	}
}
  1. 使用wait()、notify()和notifyAll时需要先对调用对象加锁。
  2. 调用wait()方法后,线程状态由running变为waiting,释放锁并放置到等待队列中。
  3. 从wait()方法返回的前提是获得了调用对象的锁。

运行过程如下图所示:
运行过程

等待/通知的经典范式

等待方遵循如下原则:

  1. 获取对象的锁
  2. 如果条件不满足,调用wait()方法,被通知后仍要检查条件
  3. 条件满足则执行对应的逻辑

对应的伪代码如下:

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

通知方遵循如下原则:

  1. 获得对象的锁
  2. 改变条件
  3. 通知对象上的线程

对应的伪代码如下:

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

Join

如果一个线程A执行了thread.join()方法语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。

线程Thread除了提供join()方法之外,还提供了join(long)和join(long, int)两个具备超时特性的方法。

一个生动的例子

public class JoinExample {

	public static void main(String[] args) throws InterruptedException {
		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(2);
		System.out.println(Thread.currentThread().getName() + " terminate.");
	}

	static class Domino implements Runnable {

		private Thread thread;

		public Domino(Thread thread) {
			this.thread = thread;
		}

		@Override
		public void run() {
			try {
				thread.join();
			} catch (InterruptedException e) {
			}
			System.out.println(Thread.currentThread().getName() + " terminate.");
		}
	}
}

输出如下:
main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.
4 terminate.
5 terminate.
6 terminate.
7 terminate.
8 terminate.
9 terminate.

可以看出每个线程终止的前提是前驱线程的终止,每个线程等待前驱线程终止后,才从join()方法返回。

ThreadLocal

线程本地存储为使用相同变量的每个不同的线程都创建不同的存储。因此,如果你有5个线程都要使用变量x所表示的对象,那么线程本地存储会生成5个用于x不同的存储块。

一个生动的例子

public class ThreadLocalExample {
	public static final ThreadLocal<Integer> X_THREADLOCAL = new ThreadLocal<>();

	public static void increment() {
		X_THREADLOCAL.set(0);
		for (int i = 0; i < 5; i++) {
			int value = X_THREADLOCAL.get();
			System.out.println(Thread.currentThread().getName() + " : " + value);
			X_THREADLOCAL.set(value + 1);
		}
	}

	public static void main(String[] args) throws InterruptedException {
		ThreadLocalExample.increment();
		new Thread(new StaticThreadLocal(), "staticThreadLocal-1").start();
		new Thread(new StaticThreadLocal(), "StaticThreadLocal-2").start();
	}

	static class StaticThreadLocal implements Runnable {
		@Override
		public void run() {
			ThreadLocalExample.increment();
		}
	}
}

运行这个程序时,可以看到每个单独的线程都被分配了自己的存储,因为它们每个都需要跟踪自己的计数值,即便只有一个X_THREADLOCAL对象。

Pipe

管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它们主要用于线程之间的数据传输,传输的媒介是内存。

管道输入/输出主要包括了4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,后两种面向字符。

一个生动的例子

package top.leagle.artofconcurrency.chapter4;

import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.util.concurrent.TimeUnit;

public class PipeExample {

	public static void main(String[] args) throws IOException {
		Sender sender = new Sender();
		Receiver receiver = new Receiver(sender);

		Thread senderThread = new Thread(sender, "SenderThrad");
		Thread receiverThread = new Thread(receiver, "ReceiverThread");
		senderThread.start();
		receiverThread.start();
	}

	static class Sender implements Runnable {
		private PipedWriter out = new PipedWriter();

		@Override
		public void run() {
			try {
				for (char c = 'A'; c <= 'C'; c++) {
					out.write(c);
					TimeUnit.SECONDS.sleep(1);
				}
			} catch (IOException e) {
				System.out.println(e + " Sender write exception");
			} catch (InterruptedException e) {
				System.out.println(e + " Sender sleep interrupted");
			}
		}

		public PipedWriter getPipedWriter() {
			return out;
		}

	}

	static class Receiver implements Runnable {
		private PipedReader in;

		public Receiver(Sender sender) throws IOException {
			in = new PipedReader(sender.getPipedWriter());
		}

		@Override
		public void run() {
			try {
				while (true) {
					System.out.println("Read " + (char) in.read());
				}
			} catch (IOException e) {
				System.out.println(e + " Receiver read exception");
			}
		}
	}
}

参考

  1. Java并发编程的艺术[书籍]

猜你喜欢

转载自blog.csdn.net/qq_21687635/article/details/84586776