1. join——插入等待:

    1) 可以在线程1中调用线程2的join方法,将线程2插入在线程1中,线程1必须等待线程2执行完后才会继续执行;

    2) 看上去很像直接调用一个普通方法(把代码贴到这个位置),但如果只join一个线程是如此,如果有多个线程各种组合join就会变得非常灵活了;

    3) 原型:public final void join(); // 即主调线程会在此等待该线程死亡后继续执行

    4) 另一个版本:void join(long millis); // 主调线程最多等待millis毫秒,超过millis毫秒还未执行完毕则不再等待

    5) 示例:

public class Test {

	public static void main(String[] args) {
		for (int i = 0; i < 30; i++) {
			if (i == 20) {
				Thread t = new Thread(() -> {
					for (int j = 0; j < 20; j++) {
						System.out.println(Thread.currentThread().getName() + " " + j);
					}
				}, "Join Thread");
				t.start();
				try {
					t.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName() + " " + i);
		}
	}

}

!可以看到主调线程在i=20处等待了;

!!必须先start再join,否则不会被插入等待!!


2. daemon——后台线程:

    1) Daemon Thread即守护线程(也叫精灵线程),通俗地说就是后台线程,它的目的一般是给其它前台的线程提供服务,比如JVM的后台垃圾处理线程;

    2) 后台线程的死亡机制:

         i. 最长不过的就是线程运行完毕自然死亡;

         ii. 其它就是抛出异常之类的非正常死亡;

         iii. 而后台线程最常见的死亡方式就是前台所有的线程都执行完毕了后台线程也随之死亡,所有操作系统的机制都是这样的,前台没线程了那么后台就没有必要存在了,所以也就强制死亡了;

!!一般后台线程都是一直存在的(程序不退出就一直都在运行),其目的就是为了给前天线程提供支撑,而前台线程则负责解决问题,既然前台都结束了就意味着问题解决完毕了,那么后台就没必要存在了,这一般也意味着程序的退出;

    3) 将一个线程对线设置成后台线程调用Thread的setDaemon方法即可:public final void setDaemon(boolean on); // on为true时将其设置为后台线程

    4) 示例:

public class Test {

	public static void main(String[] args) {
		for (int i = 0; i < 30; i++) {
			if (i == 20) {
				Thread t = new Thread(() -> {
					for (int j = 0; j < 10000; j++) {
						System.out.println(Thread.currentThread().getName() + " " + j);
					}
				}, "Join Thread");
				t.setDaemon(true);
				t.start();
			}
			System.out.println(Thread.currentThread().getName() + " " + i);
		}
	}

}

!这里就交叉执行了t和main,并且t在执行到10000之前就已经结束了,说明前台main执行完了后台线程就自动死亡了;

!!setDaemon一定要在start前调用,否则会抛出线程状态异常;

    5) 前台线程中创建的子线程默认是前提线程,后台线程中创建的子线程默认是后台线程;


3. sleep——暂停睡眠:

    1) 它是Thread的静态方法:public static native void Thread.sleep(long millis); // 调用该代码的当前线程暂停进入睡眠millis毫秒

    2) 睡眠期间不占用CPU,在此期间如果有其它线程就绪就让给其它线程执行,如果没有其它线程那睡眠的线程也不会得到执行,即强制暂停-睡眠;

    3) sleep后会进入阻塞状态;


4. yield——线程让步:

    1) yield表示让线程暂停一下,但这个暂停并不是进入阻塞状态,而只是让调度器申请一下重新调度一次,而线程本身则是进入就绪状态;

    2) 也许调度器又把这个让步的线程第一个调度出来执行,这就跟没暂停一样,如何调度是调度器的事情,你无法控制;

    3) 原型:public static native void Thread.yield();

!!它是一个静态方法,直接调用表示当前使用该代码的线程申请让步;

!!通常建议使用sleep来控制线程而不是yield:

        a. sleep会抛出异常单yield不会,因此sleep更安全;

        b. sleep比yield有更好的可以执行,因为sleep在任何系统上效果都相同,而yield随机性太高,并且在核数不同的CPU上效果有较大的差异;

    3) 虽然无法控制,但是你可以干涉,即给调度器一个参考,提醒调度器那些线程重要度高可以先调度;

    4) 调度器用线程优先级来衡量各个线程的重要性,在多个线程就绪时调度器会挑优先级高的线程先执行(而多个优先级值相等的线程谁应该先调度那就是基于操作系统的实现了,不同操作系统实现不尽相同),如果yield后有其它线程比让步的线程优先级高,那么就会真正的让步(暂停)去执行优先级较高的线程;

!!因此yield是一种合理利用资源的策略,如果在一个程序中,一个线程的重要度不高不低,而其它有些线程亟待处理(重要度特别高),如果这种重要度不高不低的线程直接阻塞去让高优先级线程可能代价太大,因此可以尝试申请让步,因为让步后仍处于就绪状态,不会暂停太久;

!!因此yield是一种利用时间资源的优秀策略;

    5) 人为设定线程的优先级:

        i. Java线程调度器中优先级是一个整数,范围是1~10([1, 10]闭区间);

        ii. 但Thread类也提供了三个静态常量表示大致的量级:MAX_PRIORITY(10,最高优先级)、MIN_PRIORITY(1,最低优先级)、NORM_PRIORITY(5,普通优先级);

        iii. 默认情况下主线程main具有普通优先级(5),每个线程的优先级和创建它的父线程的优先级相同;

        iv. 可以通过Thread类的对象方法改变和获取当前线程对象的优先级:

             a. public final void Thread.setPriority(int newPriority); // 设置新的优先级

             b. public final int Thread.getPriority(); // 获取优先级整数

!!设置优先级时尽量使用三个静态常量而不要直接指定一个整数,因为并不是所有的操作系统都支持Java的10个优先级,比如Windows2000就只提供了7个优先级,但无论如何,任何操作系统都会提供者三个静态常量所代表的优先级,因此尽量使用这三个静态常量保证最可靠的移植性;

    6) 示例:优先级高的线程比低优先级线程得到更多的执行机会

public class Test {

	public static String threadName() {
		return Thread.currentThread().getName();
	}

	public static int threadValue() {
		return Thread.currentThread().getPriority();
	}

	class Target implements Runnable {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			for (int i = 0; i < 50; i++) {
				System.out.println(threadName() + " Priority value = " + threadValue() + " " + i);
			}
		}

	}
	
	public void init() {
		Thread.currentThread().setPriority(6);
		for (int i = 0; i < 50; i++) {
			if (i == 10) {
				Thread low = new Thread(new Target(), "Low Value Thread");
				low.start();
				System.out.println("Low Thread initial value = " + low.getPriority());
				low.setPriority(Thread.MIN_PRIORITY);
				
				Thread high = new Thread(new Target(), "High Value Thread");
				high.start();
				System.out.println("High Thread initial value = " + high.getPriority());
				high.setPriority(Thread.MAX_PRIORITY);
			}
		}
	}

	public static void main(String[] args) {
		new Test().init();
	}

}