1. 线程的状态
- 在Java程序中,一个线程对象通过调用start()方法启动线程,并且在线程获取CPU时,自动执行run()方法。run()方法执行完毕,代表线程的生命周期结束。
- 在整个线程的生命周期中,线程的状态有以下6种:
-  
  - New:新建状态,新创建的线程,此时尚未调用start()方法;
- Runnable:运行状态,运行中的线程,已经调用start()方法,线程正在或即将执行run()方法;
- Blocked:阻塞状态,运行中的线程,在等待竞争锁时,被阻塞,暂不执行;
- Waiting:等待状态,运行中的线程,因为join()等方法调用,进入等待;
- Timed Waiting:计时等待状态,运行中的线程,因为执行sleep(等待毫秒值)join(等待毫秒值)等方法,进入计时等待;
- Terminated:终止状态,线程已终止,因为run()方法执行完毕。
 
- 当线程启动后,它可以在Runnable、Blocked、Waiting和Timed Waiting这几个状态之间切换,直到最后变成Terminated状态,线程终止

- 线程终止的原因有:
-  
  - 线程正常终止:run()方法执行到return语句返回;
- 线程意外终止:run()方法因为未捕获的异常导致线程终止;
- 对某个线程的Thread实例调用stop()方法强制终止(宇宙超级无敌强烈不推荐);
 
2. 线程的插队:join( )方法
2.1 join( )方法的作用
-  
  - t.join()方法会使当前线程( 主线程 或者调用t.join()的线程 )进入等待池,并等待 线程t 执行完毕后才会被唤醒。此时,并不影响同一时刻处在运行状态的其他线程。
 
示例:
myThread.join()被主线程调用,则主线程进入WAITING或者TIMED_WAITING(调用myThread.join(long millis))等待状态,主线程Main必须等子线程myThread执行完毕后才能继续执行。当子线程myThread执行完毕后,进入TERMINATED终止状态,会自动调用notifyAll()方法,唤醒主线程,主线程重新进入RUNNABLE运行状态,继续执行;
public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程Main:开始执行,即将创建并调用子线程");
        // 创建并启动子线程
        MyThread myThread = new MyThread();
        myThread.start();
        // 主线程调用myThread子线程的join()方法
        myThread.join(); // 子线程插队,插入到当前线程main的执行序列前
        
        System.out.println("主线程Main:当子线程myThread执行完毕后,主线程Main再执行");
    }
}public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("子线程:开始执行");
        int sencondValue = (int)(Math.random()*1000);
        try {
            Thread.sleep(sencondValue);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子线程:结束执行,耗时"+sencondValue+"毫秒");
    }
}2.2 join( )方法的实现原理
-  
  - join()方法的底层是利用wait()方法实现;
- join()方法是一个synchronized同步方法,当主线程调用 线程t.join( )方法时,主线程先获得了 线程t对象 的锁,随后进入join()方法,调用 线程t对象 的wait()方法,使主线程进入了 线程t对象 的等待池;
- 等到 线程t 执行完毕之后,线程在TERMINATED终止状态的时候会自动调用自身的notifyAll()方法,来唤醒所有处于等待状态的线程:这个机制在隐藏在native本地方法中,由一个C++实现的方法ensure_join()函数实现。在该函数的尾部,执行了lock.notify_all(thread);,相当于调用了notifyAll()方法。
 
static void ensure_join(JavaThread*thread) {
    Handle threadObj(thread, thread -> threadObj());
    ObjectLocker lock(threadObj, thread);
    thread -> clear_pending_exception();
    java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
    java_lang_Thread::set_thread(threadObj(), NULL);
    //下行执行了notifyAll()操作
    lock.notify_all(thread);
    thread -> clear_pending_exception();
}- 综上所述:join()方法实际上是通过调用wait()方法, 来实现同步的效果的。
-  
  - 例如:A线程中调用了B线程的join()方法,则相当于A线程调用了B线程的wait()方法,在调用了B线程的wait()方法后,A线程就会进入WAITING或者TIMED_WAITING等待状态,因为它相当于放弃了CPU的使用权。
 
- 注意:join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕:即join(0)=join();
public class Thread implements Runnable
    public final void join() throws InterruptedException {
       join(0);
    }
	public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {
            while (isAlive()) {
                // 无限等待
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                // 计时等待
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
}阅读代码,分析运行结果:
public class Main {
    
    private static void printWithThread(String content) {
        System.out.println("[" + Thread.currentThread().getName() + "线程]: " + content);
    }
    
    public static void main(String[] args) {
        printWithThread("开始执行main方法");
        
        Thread myThread = new Thread(new Runnable() {
            @Override
            public void run() {
                printWithThread("我在自定义的线程的run方法里");
                printWithThread("我马上要休息1秒钟, 并让出CPU给别的线程使用.");
                try {
                    Thread.sleep(1000);
                    printWithThread("已经休息了1秒, 又重新获得了CPU");
                    printWithThread("我休息好了, 马上就退出了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        try {
            myThread.start();
            printWithThread("我在main方法里面, 我要等下面这个线程执行完了才能继续往下执行.");
            myThread.join();
            printWithThread("我在main方法里面, 马上就要退出了.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}运行结果:
[main线程]: 开始执行main方法
[main线程]: 我在main方法里面, 我要等下面这个线程执行完了才能继续往下执行.
[Thread-0线程]: 我在自定义的线程的run方法里
[Thread-0线程]: 我马上要休息1秒钟, 并让出CPU给别的线程使用.
[Thread-0线程]: 已经休息了1秒, 又重新获得了CPU
[Thread-0线程]: 我休息好了, 马上就退出了
[main线程]: 我在main方法里面, 马上就要退出了.运行结果分析:
我们在main方法中调用了myThread.join(),上面这段代码有两个线程:一个是执行main方法的main线程,一个是我们自定义的Thread-0线程。
- Thread-0线程是myThread对象代表的子线程;
- main线程在等Thread-0线程的终止,因为我们在main方法中调用了myThread.join();
- Thread-0线程中途让出了CPU, main线程还是必须等到其执行完毕了才能继续往下执行;
如果将myThread.join()改为myThread.join(500),代表main线程最多等500毫秒(0.5秒),运行结果如下:
[main线程]: 开始执行main方法
[main线程]: 我在main方法里面, 我要等下面这个线程执行完了才能继续往下执行.
[Thread-0线程]: 我在自定义的线程的run方法里
[Thread-0线程]: 我马上要休息1秒钟, 并让出CPU给别的线程使用.
[main线程]: 我在main方法里面, 马上就要退出了.
[Thread-0线程]: 已经休息了1秒, 又重新获得了CPU
[Thread-0线程]: 我休息好了, 马上就退出了运行结果分析:
main线程在等Thread-0线程的过程中,设置等待500毫秒,而Thread-0线程休眠1000毫秒,所以main线程先执行输出,Thread-0线程休眠结束后,恢复执行;
2.3 join( )方法和sleep( )方法的区别
-  
  - 两个方法都可以实现类似"线程等待"的效果,但是仍然有区别;
- join()是通过在内部使用synchronized + wait()方法来实现的,所以join()方法调用结束后,会释放锁;
- sleep()休眠没有结束前,不会释放锁;
 
3. 线程的中断:interrupt( )方法
如果线程需要执行一个长时间任务,就可能需要能中断线程。中断线程就是其他线程给该线程发一个信号,该线程收到信号后结束执行run()方法,使得自身线程能立刻结束运行。
例如:假设从网络下载一个100M的文件,如果网速很慢,用户等得不耐烦,就可能在下载过程中点“取消”,这时,程序就需要中断下载线程的执行。
中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。
3.1 interrupt( )方法的作用
interrupt()方法的作用是设置该线程的中断状态为true,线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于中断状态。线程会不时地检测这个中断状态值,以判断线程是否应该被中断(中断状态值是否为true)。
3.2 interrupt( )方法的原理
interrupt()方法只是改变中断状态,不会像stop()中断一个正在运行的线程。支持线程中断的方法(Thread.sleep() 、join()、wait()等方法)就是在监视线程的中断状态,一旦发现线程的中断状态值被置为“true”,就会抛出线程中断的异常InterruptedException,给WAITING或者TIMED_WAITING等待状态的线程发出一个中断信号,线程检查中断标识,就会以退出WAITING或者TIMED_WAITING等待状态;
注意事项:
- 线程被Object.wait(), Thread.join()和Thread.sleep()三种方法阻塞或等待,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常,从而提前终结被阻塞状态。
- 如果线程没有被阻塞或等待,调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()等方法进入阻塞或等待时,才会抛出 InterruptedException异常;
案例1:子线程休眠时,可以通过interrupt()中断子线程;
public class Main {
	public static void main(String[] args) {
	    // 创建子线程
	    Thread thread = new Thread() {
	        public void run() {
	            System.out.println("子线程开始执行,进入RUNNABLE状态");
	            try {
	                // 子线程休眠6秒,进入TIMED_WAIT计时等待状态
	                Thread.sleep(1000 * 6); 
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	                System.out.println("子线程中断,进入TERMINATED状态");
	                return;
	            }
	            System.out.println("子线程结束执行,进入TERMINATED状态");
	        }
	    };
	    
	    // 启动子线程
	    thread.start();
		
	    // main主线程休眠5秒
	    try {
	        Thread.sleep(1000 * 3); 
	    } catch (InterruptedException e) {
	        e.printStackTrace();
	    }
	    
	     // main主线程修改子线程的中断状态=true
	     // 子线程检测中断状态=true,则抛出InterruptedException,子线程执行结束
	     thread.interrupt();
	}
}案例2:子线程等待时,可以通过interrupt()中断子线程
public class Main {
    public static void main(String[] args) throws InterruptedException {
    	System.out.println("主线程:开始执行");
    	
    	// main主线程创建子线程MyThread
    	MyThread t = new MyThread();
        t.start();
        
        Thread.sleep(1000);
        t.interrupt(); // 中断t线程
        t.join(); // 等待t线程结束
        
        System.out.println("主线程:结束执行");
    }
}
class MyThread extends Thread {
    public void run() {
    	System.out.println("MyThread线程:开始执行");
    	
    	// MyThread线程创建子线程HelloThread
    	HelloThread hello = new HelloThread();
        hello.start(); // 启动HelloThread线程
        try {
            hello.join(); // 等待hello线程结束
        } catch (InterruptedException e) {
            System.out.println("MyThread线程:结束执行,interrupted!");
        }
        
        // MyThead线程结束后,中断子线程HelloThread
        hello.interrupt();
    }
}
class HelloThread extends Thread {
    public void run() {
    	System.out.println("Hello线程:开始执行");
        int n = 0;
        while (!isInterrupted()) {
            n++;
            System.out.println(n + " hello!");
        }
        System.out.println("Hello线程:结束执行!");
    }
}案例3:子线程运行时,可以通过isInterrupted()随时观察子线程的中断状态
public class Main {
	public static void main(String[] args) throws InterruptedException {
		System.out.println("main主线程:开始执行");
        
        // 创建2个子线程
		Thread t1 = new Thread("线程1") {
			@Override
			public void run() {
				System.out.println(getName() + ":开始执行");
				while(!isInterrupted()) {
					System.out.println(UUID.randomUUID());
				}
				System.out.println(getName() + ":结束执行");
			}
		};
		
		Thread t2 = new Thread("线程2") {
			@Override
			public void run() {
				System.out.println(getName() + ":开始执行");
				while(!isInterrupted()) {
					System.out.println((int)(Math.random()*10000));
				}
				System.out.println(getName() + ":结束执行");
			}
		};
        // 启动子线程
		t1.start();
		t2.start();
        // 主线程休眠10毫秒
		Thread.sleep(10);
        // 10毫秒后,中断子线程1
		t1.interrupt();
        // 子线程1执行结束后,继续执行主线程
		t1.join();
		System.out.println("main主线程:结束执行");
        // 主线程执行结束后,中断子线程2
        // 子线程1的中断,不会影响子线程2
		t2.interrupt();
	}
}4. 线程的让出:yield( )方法
4.1 yield( )方法的作用
- 线程通过调用yield()方法告诉JVM的线程调度,当前线程愿意让出CPU给其他线程使用。
- 至于系统是否采纳,取决于JVM的线程调度模型:分时调度模型和抢占式调度模型
-  
  - 分时调度模型:所有的线程轮流获得 cpu的使用权,并且平均分配每个线程占用的 CPU 时间片;
- 抢占式调度模型:优先让可运行池中优先级高的线程占用 CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。(JVM虚拟机采用的是抢占式调度模型 )
 
/**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     * 向调度程序发出的提示,表示当前线程愿意让出处理器的使用权
     */
    public static native void yield();案例1:子线程2执行过程中,通过yield()让出CPU,使子线程1执行概率变高。
public static void main(String[] args) {
    // 创建子线程1:打印字母A-Z
    Thread thread1 = new Thread() {
        public void run() {
            for (char c = 'A'; c <= 'Z'; c++) {
                System.out.println(c);
            }
        }
    };
    // 创建子线程2:打印数字65-90
    Thread thread2 = new Thread() {
        public void run() {
            Thread.yield(); // 让当前线程让出CPU
             for (int c = 65; c <= 90; c++) {
                System.out.println(c);
                // Thread.yield(); // 让当前线程让出CPU
            }
        }
    };
    // 启动子线程
    thread1.start();
    thread2.start();
}5. 守护线程(Daemon Thread)
5.1 用户线程与守护线程的区别
- 用户线程:我们平常创建的普通线程;
- 守护线程:用来服务于用户线程的线程,在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出;而守护线程执行结束后,虚拟机不会自动退出。
5.2 设置守护线程
- 在调用start()方法前,调用setDaemon(true)把该线程标记为守护线程
5. 守护线程(Daemon Thread)
5.1 用户线程与守护线程的区别
○
用户线程:我们平常创建的普通线程;
○
守护线程:用来服务于用户线程的线程,在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出;而守护线程执行结束后,虚拟机不会自动退出。
5.2 设置守护线程
○
在调用start()方法前,调用setDaemon(true)把该线程标记为守护线程案例1:用户线程会影响JVM退出
public class Main {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        // 创建并启动子线程
        new Thread() {
            @Override
            public void run() {
                //子线程休眠10秒钟
                try {
                    Thread.sleep(10*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("普通用户线程,运行耗时" + (System.currentTimeMillis() - startTime));
            }
        }.start();
        //主线程休眠3秒,确保在子线程之前结束休眠
        try {
            Thread.sleep(3*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main主线程,运行耗时 " + (System.currentTimeMillis() - startTime));
    }
}运行结果分析:普通用户线程,在没有完成打印内容的时候,JVM是不会被结束。
Main主线程,运行耗时 3001
普通用户线程,运行耗时10001
Process finished with exit code 0案例2:守护线程不会影响JVM退出
public class Main {
    
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        // 创建子线程
        Thread daemonThread = new Thread() {
            @Override
            public void run() {
                //子线程休眠10秒钟
                try {
                    Thread.sleep(10*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("守护线程,运行耗时" + (System.currentTimeMillis() - startTime));
            }
        };
        daemonThread.setDaemon(true); // 启动线程前,设置子线程为守护线程
        daemonThread.start(); // 启动线程
        //主线程休眠3秒,确保在子线程之前结束休眠
        try {
            Thread.sleep(3*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main主线程,运行耗时 " + (System.currentTimeMillis() - startTime));
    }
}运行结果分析:守护线程,还没有打印出来内容的时候,JVM就已经结束了。
Main主线程,运行耗时 3001
Process finished with exit code 0案例3:用户线程的中断结束后,主线程的执行结束,会导致JVM的结束退出
public class Main {
	public static void main(String[] args) throws InterruptedException {
		// 守护线程t1
		Thread t1 = new Thread() {
			@Override
			public void run() {
				while(true) {
					System.out.println("守护线程t1 while loop.....");
				}
			}
		};
		t1.setDaemon(true); // 设置守护线程
		t1.start();
		
		// 用户线程t2
		Thread t2 = new Thread() {
			@Override
			public void run() {
				while(!isInterrupted()) {
					System.out.println("用户线程t2 while loop.....");
				}
				System.out.println("用户线程t2线程结束(中断)");
			}
		};
		t2.start();
		
		// 主线程休眠1000毫秒后,中断用户线程t2
		Thread.sleep(1000);
		t2.interrupt(); // 中断用户线程t2
		
		// 主线程执行结束
		System.out.println("main主线程结束。。。。");
		
		// 守护线程自动结束
	}
}总结
- 线程的状态有以下6种:New、Runnable、Blocked、Waiting、Timed Waiting、Terminated。
- join()方法用于实现线程插队,调用完毕后会释放锁。
- sleep()方法用于实现线程休眠,调用完毕后不会释放锁。
- interrupt()方法用于设置该线程的中断状态为true。
- yield()方法用于让出CPU执行。
- JVM的线程调度模型:分时调度模型和抢占式调度模型。



















