[关闭]
@xtccc 2018-05-20T10:54:09.000000Z 字数 8927 阅读 2725

Thread

给我写信
GitHub

此处输入图片的描述


Java


目录


1. 关于Thread


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会一直运行直到以下事件发生:




2. 创建线程


创建新的线程有两种方法:




方法1与方法2的结构是非常相似的,daemon threads的行为也是相同的。实际上,应优先使用方法2,因为实现一个接口,比扩展一个基类的目的更加明确,也更加明了。

java.lang.Thread也是基于 java.lang.Runnable的:

  1. public class Thread implements Runnable {
  2. }




3. 怎样终止daemon threads


3.1 调用System#exit

如果non-daemon线程调用了System.exit或者Runtime#getRuntime#exit,则它创建的所有daemon线程会立即终止:

  1. // 这是main方法
  2. System.out.println("进入main");
  3. ThreadClzA t = new ThreadClzA("堂吉诃德");
  4. t.start();
  5. System.out.println("离开main");
  6. System.exit();

运行结果如下:
QQ20160105-17@2x.png-38.7kB


3.2 所有user threads(non-daemon threads)都运行完毕

通过方法Thread#setDaemon,可以将一个thread设置为user thread或者daemon thread,user thread又称为non-daemon thread

如果所有的user threads,都运行完毕了,那么JVM就会退出,即使仍然存在daemon threads在运行。

如果daemon threads抛出了异常,也可以在运行的界面上看到输出的异常信息。

  1. System.out.println("进入main");
  2. ThreadClzA t = new ThreadClzA("堂吉诃德");
  3. t.setDaemon(true);
  4. t.start();
  5. Thread.sleep(15000);
  6. System.out.println("离开main");

运行结果:
QQ20160106-10@2x.png-45.1kB

3.3 抛出异常

3.3.1. 如果被创建的daemon thread抛出了异常

如果被创建的daemon thread抛出了异常,则它会立即结束,但是不会影响到创建它的主线程,主线程依然会正常运行到结束。

  1. public class ThreadClzA extends Thread {
  2. private String title;
  3. public ThreadClzA(String s) {
  4. title = s;
  5. System.out.println("创建ThreadClzA线程: title = " + title);
  6. }
  7. @Override
  8. public void run(){
  9. System.out.println("ThreadClzA线程 - " + title + " 进入`run`");
  10. Random rand = new Random(System.currentTimeMillis());
  11. for (int i=0; i < 5; i++) {
  12. /** 制造异常 */
  13. if (3 ==i)
  14. System.out.println(101/0);
  15. int seconds = rand.nextInt(10);
  16. System.out.println("ThreadClzA线程 - " + title + ",第 " + i + " 次循环," +
  17. "我将沉睡 " + seconds + "秒 ... ");
  18. try {
  19. sleep(seconds*1000);
  20. } catch (InterruptedException ex) {
  21. System.out.println("ThreadClzA线程 " + title + " 发生异常");
  22. }
  23. }
  24. System.out.println("ThreadClzA线程 - " + title + " 退出`run`");
  25. }
  26. }
  1. // main方法
  2. System.out.println("进入main");
  3. ThreadClzA t = new ThreadClzA("哈姆雷特");
  4. t.start();
  5. /** 等待40秒 */
  6. for (int i=0; i < 5; i++) {
  7. System.out.println("主线程 - 正在等待我创建的daemon thread, i = " + i);
  8. }
  9. System.out.println("离开main");

运行结果为
QQ20160105-21@2x.png-133.1kB


3.3.2. 如果创建daemon threads的主线程抛出了异常

如果创建daemon threads的主线程抛出了异常,那么主线程会立即结束,但是被主线程创建的daemon thread仍然会正常运行到结束。

  1. public class ThreadClzA extends Thread {
  2. private String title;
  3. public ThreadClzA(String s) {
  4. title = s;
  5. System.out.println("创建ThreadClzA线程: title = " + title);
  6. }
  7. @Override
  8. public void run(){
  9. System.out.println("ThreadClzA线程 - " + title + " 进入`run`");
  10. Random rand = new Random(System.currentTimeMillis());
  11. for (int i=0; i < 5; i++) {
  12. int seconds = rand.nextInt(10);
  13. System.out.println("ThreadClzA线程 - " + title + ",第 " + i + " 次循环," +
  14. "我将沉睡 " + seconds + "秒 ... ");
  15. try {
  16. sleep(seconds*1000);
  17. } catch (InterruptedException ex) {
  18. System.out.println("ThreadClzA线程 " + title + " 发生异常");
  19. }
  20. }
  21. System.out.println("ThreadClzA线程 - " + title + " 退出`run`");
  22. }
  23. }
  1. // main方法
  2. System.out.println("进入main");
  3. ThreadClzA t = new ThreadClzA("堂吉诃德");
  4. t.start();
  5. // 主线程抛出异常
  6. System.out.println(101/0);
  7. System.out.println("离开main");

运行结果为
QQ20160105-24@2x.png-120.6kB


3.3.3. 主线程创建了线程t1,t1又创建了线程t2,t2抛出了异常

主线程创建了线程t1,t1又创建了线程t2,t2抛出了异常,t2会终止运行,但是t1和主线程的运行不受影响。




4. 处理thread抛出的异常


当新建线程抛出异常且该异常未被捕获时,我们可以为该线程提供一个方法,它可以被uncaught exception触发。设置的方式是 Thread#setUncaughtExceptionHandler,如下:

  1. public class ThreadClzE extends Thread {
  2. private String title;
  3. public ThreadClzE(String s) {
  4. title = s;
  5. System.out.println("创建ThreadClzE线程: title = " + title);
  6. }
  7. @Override
  8. public void run() {
  9. System.out.println("ThreadClzE线程 - " + title + " 进入`run`");
  10. Random rand = new Random(System.currentTimeMillis());
  11. for (int i = 0; i < 8; i++) {
  12. /** 制造一个 divide by zero 异常 */
  13. if (3 == i) {
  14. int x = 102;
  15. int y = 0;
  16. System.out.println(x / y);
  17. }
  18. int seconds = rand.nextInt(5);
  19. System.out.println("ThreadClzE线程 - " + title + ",第 " + i + " 次循环," +
  20. "我将沉睡 " + seconds + "秒 ... ");
  21. try {
  22. Thread.sleep(seconds * 1000);
  23. } catch (InterruptedException ex) {
  24. System.out.println("ThreadClzE线程 " + title + " sleep发生异常");
  25. }
  26. }
  27. System.out.println("ThreadClzE线程 - " + title + " 退出`run`");
  28. }
  29. }
  1. System.out.println("进入main");
  2. ThreadClzE t = new ThreadClzE("夏洛克");
  3. /** 提供一个异常处理函数,当被创建的线程抛出未被捕获的异常时,
  4. * 该方法将被调用一次,然后线程退出
  5. * */
  6. t.setUncaughtExceptionHandler(
  7. new Thread.UncaughtExceptionHandler() {
  8. @Override
  9. public void uncaughtException(Thread t, Throwable e) {
  10. System.out.println("这是我们自定义的异常处理函数");
  11. }
  12. }
  13. );
  14. t.start();
  15. System.out.println("即将退出main");

运行结果为:
QQ20160106-14@2x.png-79.5kB




5. 线程池


5.1 创建线程池

通过ExecutorService可以创建并使用线程池。

  1. public class BasicUsage {
  2. public static void main(String[] args) throws InterruptedException {
  3. TestExecute();
  4. }
  5. /**
  6. *
  7. * */
  8. public static void TestExecute() throws InterruptedException {
  9. /**
  10. * 创建一个大小固定的线程池(容纳3个active thread)
  11. * 加入5个线程,其中3个线程会立即调度运行,剩下的2个线程
  12. * 将等待,直到线程池中由空余的slot可以让它们运行
  13. * */
  14. ExecutorService executor = Executors.newFixedThreadPool(3);
  15. for (int i=1; i<= 5; i++) {
  16. Runnable worker = new Worker("worker" + i);
  17. executor.execute(worker); // 也可以是executor.submit(worker);
  18. }
  19. /**
  20. * 调用`shutdown()`,会等待所有的线程运行完毕,而不会立即结束正在运行的线程
  21. * 如果希望让executor中所有正在运行的线程立即结束,应该调用`shutdownNow()`
  22. * 在调用`shutdown()`之后,不能再向executor中加入新的线程了,否则会报异常
  23. * */
  24. executor.shutdown();
  25. /**
  26. * 如果不调用`shutdown()`,那么即使所有的线程都运行完毕了,
  27. * `isTerminated()`也仍然会返回false
  28. * */
  29. while (!executor.isTerminated()) {
  30. Thread.sleep(3*1000);
  31. System.out.println("***** 等待所有的线程结束 *****");
  32. }
  33. System.out.println("\nAll threads stopped .");
  34. }
  35. /**
  36. * 线程实例
  37. * */
  38. public static class Worker implements Runnable {
  39. public String title;
  40. public Worker(String title) {
  41. this.title = title;
  42. }
  43. @Override
  44. public void run() {
  45. System.out.println(title + " 开始运行 ");
  46. long seconds = new Random(System.currentTimeMillis()).nextInt(4)*1000;
  47. try {
  48. Thread.sleep(seconds);
  49. System.out.println(title + " 运行结束 ");
  50. } catch (InterruptedException e) {
  51. e.printStackTrace();
  52. }
  53. }
  54. }
  55. }

运行结果为:
QQ20160107-3@2x.png-73.3kB

5.2 处理线程池中的异常

如果某个线程出现了异常未被捕获,这个线程会立即终止,但不影响线程池中其他线程的运行。

  1. public class HandleExceptions {
  2. public static void main(String[] args) throws InterruptedException {
  3. TestThreadExceptions();
  4. }
  5. public static void TestThreadExceptions() throws InterruptedException {
  6. ExecutorService executor = Executors.newFixedThreadPool(2);
  7. executor.execute(new Bean("beanA"));
  8. executor.execute(new Bean("beanB"));
  9. executor.execute(new Bean("beanC"));
  10. executor.execute(new Bean("beanD"));
  11. executor.shutdown();
  12. while (!executor.isTerminated()) {
  13. Thread.sleep(2*1000);
  14. System.out.println("***** 等待所有的线程结束 *****");
  15. }
  16. System.out.println("\nAll threads stopped .");
  17. }
  18. public static class Bean implements Runnable {
  19. public String title;
  20. public Bean(String title) {
  21. this.title = title;
  22. }
  23. @Override
  24. public void run() {
  25. System.out.println(title + " 开始运行 ");
  26. long seconds = new Random(System.currentTimeMillis()).nextInt(8)*1000;
  27. try {
  28. Thread.sleep(seconds);
  29. /** 只让一个线程抛出异常,其他线程正常运行
  30. * */
  31. if (title.equals("beanA"))
  32. System.out.println(100/0);
  33. System.out.println(title + " 运行结束 ");
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. }
  39. }

运行结果为:
QQ20160107-4@2x.png-120.9kB




6. Interrupt

Java线程的终止——interrupt
没有完美的方案,我目前的实现是在关键点抛出异常。



7. volatile 关键字

参考 Java Volatile Keyword

volatile关键字,简而言之有以下几个作用:

  1. 解决多线程并发时的 Variable Visibility Problem

    当一个线程修改了一个volatile variable后(实际上是在CPU cache中修改的),该变量的最新值会被立即写回到main memory,这样它的最新值对其他线程是可见的。

  2. 提供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.

  3. 实现 Happens-Before Guarantee

    这与JVM和CPU的自动优化语句执行顺序有关




添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注