@xtccc
2018-05-20T02:54:09.000000Z
字数 8927
阅读 3279
Java
目录
JVM允许一个Java app有若干个并发执行的线程。一个线程可以通过方法Thread#setDaemon(true)被标记为daemon。
每个thread都有优先级,优先级越高的线程,越优先执行。当一个线程t1创建了新的新的线程t2时,t2有着与t1相同的优先级。并且,当且仅当t1是daemon thread时,t2才是daemon thread。
当JVM启动时,通常会有一个non-daemon thread(它会调用某个指定class的main方法)。JVM会一直运行直到以下事件发生:
Runtime#exit方法被调用,并且security manager允许退出run方法返回,或者抛出一个越过run方法的异常)创建新的线程有两种方法:
方法1:创建一个java.io.Thread的subclass,在其中实现run方法,然后调用该subclass的start方法
下面先创建一个名为ThreadClzA的类:
public class ThreadClzA extends Thread {private String title;public ThreadClzA(String s) {title = s;System.out.println("创建ThreadClzA线程: title = " + title);}@Overridepublic void run(){System.out.println("ThreadClzA线程 - " + title + " 进入`run`");Random rand = new Random(System.currentTimeMillis());for (int i=0; i < 5; i++) {int seconds = rand.nextInt(10);System.out.println("ThreadClzA线程 - " + title + ",第 " + i + " 次循环," +"我将沉睡 " + seconds + "秒 ... ");try {sleep(seconds*1000);} catch (InterruptedException ex) {System.out.println("ThreadClzA线程 " + title + " 发生异常");}}System.out.println("ThreadClzA线程 - " + title + " 退出`run`");}}
然后,调用这个类实例的start方法
System.out.println("进入main");ThreadClzA t = new ThreadClzA("堂吉诃德");t.start();System.out.println("离开main");
运行输出为:

可见:
ThreadClzA#start方法后,ThreadClzA#run方法会立即运行;与此同时,创建该线程的non-daemon线程(即ThreadExamples#main方法所属的线程)不会阻塞,这两个线程并发地运行ThreadExamples#main方法退出),如果被创建的daemon线程还未运行完(ThreadClzA#run方法还在运行),则JVM的主进程不会退出,而是会等待所有的non-daemon线程都运行完了才会退出。方法2:创建一个class,令其implements java.lang.Runnable,并实现其中的run方法,然后将该class的一个实例作为参数构造一个Thread实例,并调用Thread#start方法。
public class ThreadClzB implements Runnable {private String title;public ThreadClzB(String s) {title = s;System.out.println("创建ThreadClzB线程: title = " + title);}@Overridepublic void run() {System.out.println("ThreadClzB线程 - " + title + " 进入`run`");Random rand = new Random(System.currentTimeMillis());for (int i=0; i < 5; i++) {int seconds = rand.nextInt(10);System.out.println("ThreadClzB线程 - " + title + ",第 " + i + " 次循环," +"我将沉睡 " + seconds + "秒 ... ");try {Thread.sleep(seconds*1000);} catch (InterruptedException ex) {System.out.println("ThreadClzB线程 " + title + " 发生异常");}}System.out.println("ThreadClzB线程 - " + title + " 退出`run`");}}
启动这个线程时,用如下方法:
ThreadClzB t = new ThreadClzB("奥赛罗");new Thread(t).start();
方法1与方法2的结构是非常相似的,daemon threads的行为也是相同的。实际上,应优先使用方法2,因为实现一个接口,比扩展一个基类的目的更加明确,也更加明了。
java.lang.Thread也是基于 java.lang.Runnable的:
public class Thread implements Runnable {}
如果non-daemon线程调用了System.exit或者Runtime#getRuntime#exit,则它创建的所有daemon线程会立即终止:
// 这是main方法System.out.println("进入main");ThreadClzA t = new ThreadClzA("堂吉诃德");t.start();System.out.println("离开main");System.exit();
运行结果如下:

通过方法Thread#setDaemon,可以将一个thread设置为user thread或者daemon thread,user thread又称为non-daemon thread。
如果所有的user threads,都运行完毕了,那么JVM就会退出,即使仍然存在daemon threads在运行。
如果daemon threads抛出了异常,也可以在运行的界面上看到输出的异常信息。
System.out.println("进入main");ThreadClzA t = new ThreadClzA("堂吉诃德");t.setDaemon(true);t.start();Thread.sleep(15000);System.out.println("离开main");
运行结果:
如果被创建的daemon thread抛出了异常,则它会立即结束,但是不会影响到创建它的主线程,主线程依然会正常运行到结束。
public class ThreadClzA extends Thread {private String title;public ThreadClzA(String s) {title = s;System.out.println("创建ThreadClzA线程: title = " + title);}@Overridepublic void run(){System.out.println("ThreadClzA线程 - " + title + " 进入`run`");Random rand = new Random(System.currentTimeMillis());for (int i=0; i < 5; i++) {/** 制造异常 */if (3 ==i)System.out.println(101/0);int seconds = rand.nextInt(10);System.out.println("ThreadClzA线程 - " + title + ",第 " + i + " 次循环," +"我将沉睡 " + seconds + "秒 ... ");try {sleep(seconds*1000);} catch (InterruptedException ex) {System.out.println("ThreadClzA线程 " + title + " 发生异常");}}System.out.println("ThreadClzA线程 - " + title + " 退出`run`");}}
// main方法System.out.println("进入main");ThreadClzA t = new ThreadClzA("哈姆雷特");t.start();/** 等待40秒 */for (int i=0; i < 5; i++) {System.out.println("主线程 - 正在等待我创建的daemon thread, i = " + i);}System.out.println("离开main");
运行结果为

如果创建daemon threads的主线程抛出了异常,那么主线程会立即结束,但是被主线程创建的daemon thread仍然会正常运行到结束。
public class ThreadClzA extends Thread {private String title;public ThreadClzA(String s) {title = s;System.out.println("创建ThreadClzA线程: title = " + title);}@Overridepublic void run(){System.out.println("ThreadClzA线程 - " + title + " 进入`run`");Random rand = new Random(System.currentTimeMillis());for (int i=0; i < 5; i++) {int seconds = rand.nextInt(10);System.out.println("ThreadClzA线程 - " + title + ",第 " + i + " 次循环," +"我将沉睡 " + seconds + "秒 ... ");try {sleep(seconds*1000);} catch (InterruptedException ex) {System.out.println("ThreadClzA线程 " + title + " 发生异常");}}System.out.println("ThreadClzA线程 - " + title + " 退出`run`");}}
// main方法System.out.println("进入main");ThreadClzA t = new ThreadClzA("堂吉诃德");t.start();// 主线程抛出异常System.out.println(101/0);System.out.println("离开main");
运行结果为

主线程创建了线程t1,t1又创建了线程t2,t2抛出了异常,t2会终止运行,但是t1和主线程的运行不受影响。
当新建线程抛出异常且该异常未被捕获时,我们可以为该线程提供一个方法,它可以被uncaught exception触发。设置的方式是 Thread#setUncaughtExceptionHandler,如下:
public class ThreadClzE extends Thread {private String title;public ThreadClzE(String s) {title = s;System.out.println("创建ThreadClzE线程: title = " + title);}@Overridepublic void run() {System.out.println("ThreadClzE线程 - " + title + " 进入`run`");Random rand = new Random(System.currentTimeMillis());for (int i = 0; i < 8; i++) {/** 制造一个 divide by zero 异常 */if (3 == i) {int x = 102;int y = 0;System.out.println(x / y);}int seconds = rand.nextInt(5);System.out.println("ThreadClzE线程 - " + title + ",第 " + i + " 次循环," +"我将沉睡 " + seconds + "秒 ... ");try {Thread.sleep(seconds * 1000);} catch (InterruptedException ex) {System.out.println("ThreadClzE线程 " + title + " sleep发生异常");}}System.out.println("ThreadClzE线程 - " + title + " 退出`run`");}}
System.out.println("进入main");ThreadClzE t = new ThreadClzE("夏洛克");/** 提供一个异常处理函数,当被创建的线程抛出未被捕获的异常时,* 该方法将被调用一次,然后线程退出* */t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {@Overridepublic void uncaughtException(Thread t, Throwable e) {System.out.println("这是我们自定义的异常处理函数");}});t.start();System.out.println("即将退出main");
运行结果为:

通过ExecutorService可以创建并使用线程池。
public class BasicUsage {public static void main(String[] args) throws InterruptedException {TestExecute();}/**** */public static void TestExecute() throws InterruptedException {/*** 创建一个大小固定的线程池(容纳3个active thread)* 加入5个线程,其中3个线程会立即调度运行,剩下的2个线程* 将等待,直到线程池中由空余的slot可以让它们运行* */ExecutorService executor = Executors.newFixedThreadPool(3);for (int i=1; i<= 5; i++) {Runnable worker = new Worker("worker" + i);executor.execute(worker); // 也可以是executor.submit(worker);}/*** 调用`shutdown()`,会等待所有的线程运行完毕,而不会立即结束正在运行的线程* 如果希望让executor中所有正在运行的线程立即结束,应该调用`shutdownNow()`* 在调用`shutdown()`之后,不能再向executor中加入新的线程了,否则会报异常* */executor.shutdown();/*** 如果不调用`shutdown()`,那么即使所有的线程都运行完毕了,* `isTerminated()`也仍然会返回false* */while (!executor.isTerminated()) {Thread.sleep(3*1000);System.out.println("***** 等待所有的线程结束 *****");}System.out.println("\nAll threads stopped .");}/*** 线程实例* */public static class Worker implements Runnable {public String title;public Worker(String title) {this.title = title;}@Overridepublic void run() {System.out.println(title + " 开始运行 ");long seconds = new Random(System.currentTimeMillis()).nextInt(4)*1000;try {Thread.sleep(seconds);System.out.println(title + " 运行结束 ");} catch (InterruptedException e) {e.printStackTrace();}}}}
运行结果为:
如果某个线程出现了异常未被捕获,这个线程会立即终止,但不影响线程池中其他线程的运行。
public class HandleExceptions {public static void main(String[] args) throws InterruptedException {TestThreadExceptions();}public static void TestThreadExceptions() throws InterruptedException {ExecutorService executor = Executors.newFixedThreadPool(2);executor.execute(new Bean("beanA"));executor.execute(new Bean("beanB"));executor.execute(new Bean("beanC"));executor.execute(new Bean("beanD"));executor.shutdown();while (!executor.isTerminated()) {Thread.sleep(2*1000);System.out.println("***** 等待所有的线程结束 *****");}System.out.println("\nAll threads stopped .");}public static class Bean implements Runnable {public String title;public Bean(String title) {this.title = title;}@Overridepublic void run() {System.out.println(title + " 开始运行 ");long seconds = new Random(System.currentTimeMillis()).nextInt(8)*1000;try {Thread.sleep(seconds);/** 只让一个线程抛出异常,其他线程正常运行* */if (title.equals("beanA"))System.out.println(100/0);System.out.println(title + " 运行结束 ");} catch (InterruptedException e) {e.printStackTrace();}}}}
运行结果为:

Java线程的终止——interrupt
没有完美的方案,我目前的实现是在关键点抛出异常。
volatile关键字,简而言之有以下几个作用:
解决多线程并发时的 Variable Visibility Problem
当一个线程修改了一个volatile variable后(实际上是在CPU cache中修改的),该变量的最新值会被立即写回到main memory,这样它的最新值对其他线程是可见的。
提供Full volatile Visibility Guarantee
If Thread A writes to a volatile variable and Thread B subsequently reads the same volatile variable, then all variables visible to Thread A before writing the volatile variable, will also be visible to Thread B after it has read the volatile variable.
If Thread A reads a volatile variable, then all variables visible to Thread A when reading the volatile variable will also be re-read from main memory.
实现 Happens-Before Guarantee
这与JVM和CPU的自动优化语句执行顺序有关