[关闭]
@lemonguge 2015-07-07T05:21:47.000000Z 字数 6933 阅读 346

并发性和多线程(四)

Concurrency


线程的优先级

线程的优先级用1-10之间的整数表示,优先级0是为虚拟机保留的。数值越大优先级越高,默认的优先级为5。线程的优先级越高获取到CPU执行权的几率也越高,注意,线程的优先级仍然无法保障线程的执行次序,优先级低的并非没机会执行(执行的几率比较低)。

  1. public class Prior {
  2. public static void main(String[] args) {
  3. showPrior(); // 查看当前线程的优先级
  4. }
  5. public static void showPrior() {
  6. System.out.println(Thread.currentThread().getName() + ":"
  7. + Thread.currentThread().getPriority());
  8. }
  9. } /* Output:
  10. main:5
  11. *///:~

正如上面的输出结果所展示的,main主线程的优先级默认为5。我们可以通过setPriority方法自定义线程的优先级,在Thread类中有三个预定义的常量用于表示优先级别:

  1. class Tick implements Runnable {
  2. private int num = 50000; // 5万张票
  3. @Override
  4. public void run() {
  5. int sell = 0; // 记录当前线程卖出票的数量
  6. while (true)
  7. synchronized (this) {
  8. if (num > 0) {
  9. num--;
  10. sell++;
  11. } else
  12. break;
  13. }
  14. System.out.println(Thread.currentThread().getName() + ":" + sell);
  15. }
  16. }
  17. public class Prior {
  18. public static void main(String[] args) {
  19. Tick tick = new Tick();
  20. Thread t1 = new Thread(tick, "电话卖票");
  21. Thread t2 = new Thread(tick, "网络卖票");
  22. // 希望票更多从网络售票渠道卖出
  23. t1.setPriority(Thread.MIN_PRIORITY);
  24. t2.setPriority(Thread.MAX_PRIORITY);
  25. t1.start();
  26. t2.start();
  27. }
  28. } /* Output: // 输出顺序和结果并不确定
  29. 电话卖票:12406
  30. 网络卖票:37594
  31. *///:~

在一个线程中开启另外一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同

  1. class Run implements Runnable{
  2. @Override
  3. public void run() {
  4. Prior.showPrior();
  5. }
  6. }
  7. public class Prior {
  8. public static void main(String[] args) throws InterruptedException {
  9. ThreadPrior();
  10. }
  11. public static void ThreadPrior() throws InterruptedException {
  12. showPrior();
  13. Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
  14. showPrior();
  15. new Thread(new Run()).start();
  16. Thread.sleep(50); // 使输出分开,效果更易查看
  17. Thread.currentThread().setPriority(7); // 设置优先级为7
  18. showPrior();
  19. new Thread(new Run()).start();
  20. }
  21. } /* Output:
  22. main:5
  23. main:1
  24. Thread-0:1
  25. main:7
  26. Thread-1:7
  27. *///:~

操作系统为所有分属于每一优先级的线程维护这单独的队列。拥有较高优先级的线程先获得CPU的执行权,操作系统会周期性地提升这些饥饿线程的优先级,直到它们达到当前执行线程的优先级,每个线程最终会这这个级别获得CPU执行权,一旦CPU进行运行切换时,线程将会被归还回到最初的优先级队列中。以上描述的是一种通用的线程调度。


Thread的其他方法

线程的让步

Thread类中有一个static void yield()静态方法,查看API的介绍“暂停当前正在执行的线程对象,并执行其他线程。”使当前线程释放执行权,处于临时阻塞状态,因此当前线程也有可以继续获得CPU执行权而将处于运行状态。

  1. class Yid implements Runnable{
  2. @Override
  3. public void run() {
  4. for(int i=0;i<50;i++){
  5. System.out.println(Thread.currentThread().getName()+":"+i);
  6. Thread.yield(); // 暂停当前线程
  7. }
  8. }
  9. }
  10. public class YieldThread {
  11. public static void main(String[] args) {
  12. new Thread(new Yid()).start();
  13. new Thread(new Yid()).start();
  14. }
  15. } /* Output: // 输出顺序并不确定,以下输出语句的一部分
  16. ...
  17. Thread-0:48
  18. Thread-1:42
  19. Thread-0:49
  20. Thread-1:43
  21. Thread-1:44
  22. ...
  23. *///:~

因此,使用yield()方法的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()方法达到让步目的,因为让步的线程还有可能被CPU运行切换再次选中而导致没有效果。

合并线程

线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时可以使用非静态join()方法,这将使当前线程处于冻结状态。

  1. class Joi implements Runnable {
  2. @Override
  3. public void run() {
  4. try { Thread.sleep(1000); } catch (InterruptedException e) { }
  5. for (int i = 1; i <= 3; i++)
  6. System.out.println(Thread.currentThread().getName() + ":" + i);
  7. }
  8. }
  9. public class JoinThread {
  10. public static void main(String[] args) throws InterruptedException {
  11. Thread t1 = new Thread(new Joi());
  12. t1.start(); // t1线程将睡1秒
  13. for (int i = 1; i <= 3; i++) {
  14. System.out.println(Thread.currentThread().getName() + ":" + i);
  15. if (i == 2) {
  16. t1.join(); // 使主线程的执行必须在t1线程之后
  17. }
  18. }
  19. }
  20. } /* Output:
  21. main:1
  22. main:2
  23. Thread-0:1
  24. Thread-0:2
  25. Thread-0:3
  26. main:3
  27. *///:~

正如输出所展示的,如果没有t1.join();语句,首先打印的肯定是主线程的三个数字。正是由于t1线程的加入,使得要先执行完t1线程之后才可以执行main线程。查看API可以发现join方法还有两个需要指定参数的重载版本,这三个方法如下所示:

  1. class Joi implements Runnable {
  2. @Override
  3. public void run() {
  4. try { Thread.sleep(1000); } catch (InterruptedException e) { }
  5. for (int i = 1; i <= 3; i++)
  6. System.out.println(Thread.currentThread().getName() + ":" + i);
  7. }
  8. }
  9. public class JoinThread {
  10. public static void main(String[] args) throws InterruptedException {
  11. Thread t1 = new Thread(new Joi());
  12. t1.start(); // t1线程将睡1秒
  13. for (int i = 1; i <= 3; i++) {
  14. System.out.println(Thread.currentThread().getName() + ":" + i);
  15. if (i == 2) {
  16. t1.join(50); // 主线程最长等待50ms
  17. }
  18. }
  19. }
  20. } /* Output:
  21. main:1
  22. main:2
  23. main:3
  24. Thread-0:1
  25. Thread-0:2
  26. Thread-0:3
  27. *///:~

可以看出一旦等待超时,main线程将不再等待而退出了冻结状态,将获得CPU的执行权而处于运行状态。

守护线程

守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。每个Java应用程序至少会有一个非守护线程,即主线程。

守护线程又称后台线程或用户线程,在创建守护线程的父线程结束时,守护线程一定会结束,无论是否处于冻结状态。该方法必须在启动线程前调用

守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。

  1. class Daemon implements Runnable{
  2. @Override
  3. public void run() {
  4. for(int i=1;i<=3;i++){
  5. System.out.println(Thread.currentThread().getName() + ":" + i);
  6. try { Thread.sleep(800); } catch (InterruptedException e) { }
  7. }
  8. }
  9. }
  10. public class DaemonThread {
  11. public static void main(String[] args) throws InterruptedException {
  12. Thread dae = new Thread(new Daemon());
  13. dae.setDaemon(true); // 设为后台线程
  14. dae.start();
  15. Thread.sleep(1000);
  16. for(int i=1;i<=3;i++){
  17. System.out.println(Thread.currentThread().getName() + ":" + i);
  18. }
  19. }
  20. } /* Output:
  21. Thread-0:1
  22. Thread-0:2
  23. main:1
  24. main:2
  25. main:3
  26. *///:~

创建新的线程会成为守护线程,当且仅当创建新线程对象的那个线程是守护线程。

中断线程

Thread类中可以发现有三个关于中断线程的方法:

值得注意的是:当线程被中断时,线程就会将内部中断标志设置为true。当你调用interrupt()方法来中断线程时,并不意味着无论线程在做什么都将会停止。最近看到了一个很好的例子:该方法就像是你拍朋友的肩膀一样,他可能忽略你的打扰,并继续干他的工作。然而过了一段时间后,他可能想起你拍了他并听你说话,这和线程发生的情况类似。

  1. public class Interrupt {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread thread = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. while (true){ }
  7. }
  8. });
  9. // 设置为守护线程,当主线程结束,该线程也将结束
  10. thread.setDaemon(true);
  11. thread.start();
  12. Thread.sleep(10);
  13. // 中断线程
  14. thread.interrupt();
  15. Thread.sleep(10);
  16. // 判断线程是否被中断
  17. System.out.println(thread.isInterrupted() ? "线程被中断" : "线程未中断");
  18. }
  19. } /* Output:
  20. 线程未中断
  21. *///:~

如果在线程任务代码内部进行线程是否中断的判断时,将会导致线程中断被忽略。要么通过Thread.currentThread().isInterrupted(),要么通过Thread.interrupted()。以下示例将使用第一种来进行线程中断判断:

  1. public class Interrupt {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread thread = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. while (true){
  7. // 判断中断标志将使线程中断被忽略
  8. if(Thread.currentThread().isInterrupted()){
  9. System.out.println("当前线程被中断");
  10. break;
  11. }
  12. }
  13. }
  14. });
  15. // 设置为守护线程,当主线程结束,该线程也将结束
  16. thread.setDaemon(true);
  17. thread.start();
  18. Thread.sleep(10);
  19. // 中断线程
  20. thread.interrupt();
  21. Thread.sleep(10);
  22. // 判断线程是否被中断
  23. System.out.println(thread.isInterrupted() ? "线程被中断" : "线程未中断");
  24. }
  25. } /* Output:
  26. 当前线程被中断
  27. 线程未中断
  28. *///:~

从输出结果可以发现主线程打印了“线程未中断”的输出结果。

对于interrupted()静态方法,如果连续两次调用该方法,则第二次调用将返回false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。interrupt()方法中断一个处于冻结状态的线程时,其中断状态将被清除(将返回false),它还将收到一个InterruptedException

  1. public class Interrupt {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread thread = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. try {
  7. Thread.sleep(5000);
  8. } catch (InterruptedException e) {
  9. // 中断处于冻结状态的线程,中断标志将为false
  10. System.out.println(Thread.currentThread().isInterrupted());
  11. // 重建中断标志,将标志为true
  12. Thread.currentThread().interrupt();
  13. // 静态方法的中断判断将会清楚中断标志
  14. System.out.println(Thread.interrupted() ? "当前线程被中断" : "当前线程未中断");
  15. System.out.println(Thread.currentThread().isInterrupted());
  16. }
  17. }
  18. });
  19. // 设置为守护线程,当主线程结束,该线程也将结束
  20. thread.setDaemon(true);
  21. thread.start();
  22. Thread.sleep(1000);
  23. // 中断线程
  24. thread.interrupt();
  25. Thread.sleep(1000);
  26. // 判断线程是否被中断
  27. System.out.println(thread.isInterrupted() ? "线程被中断" : "线程未中断");
  28. }
  29. } /* Output:
  30. false
  31. 当前线程被中断
  32. false
  33. 线程未中断
  34. *///:~

到现在为止,传统的多线程技术应该就是这些了,至于为什么不写关于线程组ThreadGroup的知识,因为在Java SE5中为线程集合引入的更好的操作特性,使得线程组的使用有些多余。

后记:最近在看《Java Programming》这本书的多线程几章,打算看完之后开始写写多线程的新特性。为了写好新特性,所以花了几天将传统的多线程技术进行了回顾,写于2015年6月29日。

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