第四章 Java并发编程基础

1基本概念

1.1什是线程?

线程是系统调度的最小单位。一个进程中可以包含多个线程;处理器会在线程上高速切换,就像是多个线程同时在执行。

1.2线程的特点

线程含有自己的计数器。堆栈、局部变量等属性,并能够访问共享的内存变量。

1.3为何使用多线程

  1. 硬件的快速发展:处理器的性能得到巨大的提升的同时,越来越多的PC采用了多核处理器。这样,每个线程平均能分配到的时间片就变多了,硬件的发展,提升了多线程的处理能力。
  2. 更快的响应时间:多线程的使用,多个线程几乎同时在执行,将任务分拆交由不同线程去执行,降低了运算时间,给用户带来更快的响应速度。

1.4线程优先级

现代才做系统大多数采用时分的形式调度线程。系统会分配时间片,每个线程都有机会被分配到若干时间片,时间片就是线程能够调用处理器资源的时间。线程优先级高的线程,将会有更多的机会分配到时间片。

Java使用priority来控制线程的优先级。可以使用setPriority(int)来控制优先级。其中,优先级的范围为1~10,默认优先级是5。

image.png

tips:操作系统不一定会理会Java对于优先级的设置,最终的线程优先级结果来自于操作系统自己的调度。

1.5线程的状态

一般操作系统中,线程状态有五种:

新建状态(New)
就绪状态(Runnable)
运行状态(Running)
阻塞状态(Blocked)
死亡状态(Dead)

Java中个线程定义了6种状态,分别是:

  1. NEW : 初始状态,线程被创建,但没有start;
  2. RUNNABLE : 运行状态(就绪状态——表示可以运行,但不一定运行,可能需要等待分配时间片、运行状态——当线程获得时间片后进入运行状态,真正开始执行run()方法),java将这两种状态合并成了一种状态;
  3. BLOCKED:阻塞状态,获取不到锁,被阻塞了;
  4. WAITING: 等待状态,需要其他线程来notify或者intercept;
  5. TIME_WAITING: 超时等待状态,与WAITING相比,它会在指定时间内自动返回;
  6. TERMINATED: 终止状态,表示线程执行完毕了。

image.png

1.6Daemon线程

Daemon线程是一种支持型线程,一般作为后台调度以及支持型工作。
设置线程为Deamon线程

setDaemon(boolean flag)

 Thread deamonThread = new Thread(new Runnable() {
            public void run() {
                System.out.println("test1");
            }
        });
deamonThread.setDaemon(true);

tips:

当JVM中不存在非Daemon线程的时候,JVM将会退出。

这个就比较厉害了,也就是说,其实是存在某种情况下,某些代码可能无法执行。

2 中断Interrupt

2.1什么是中断

中断的意思不是停止(stop)一个线程,而是去“提醒”,并且采用了让这个线程抛出异常的方式去“提醒”这个线程——你该干嘛了。具体要让这个线程干嘛?这个不好说,不同的场景下,可能还不太不一样。

其中,要注意的是:通过一个线程去向另一个线程发出“提醒”信号,抛出异常的不是发出信号的线程,而是被通知的线程抛出了异常。
假设,线程a中:

new Thread(new Runnable() {
            public void run() {
                try {
                    while (true){
                        Thread.sleep(1000);
                        System.out.println("b");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
}).start();

try {
            Thread.sleep(2000);
} catch (InterruptedException e) {
            e.printStackTrace();
 }
b.interrupt();

上述代码中,在a线程通知了b线程去中断,所以在b线程中抛出异常。

2.2什么时候抛中断异常

通知的时候,也不是马上就中断,并抛出异常。interrupt()只是将修改了中断标志位。
源码如下:

 public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

有趣的是,就算一个线程被调用了interrupt()方法,也不一定就会抛出InterruptedException,只有当这个线程调用了能够抛出InterruptedException的方法的时候才会抛出异常,比如sleep、join、wait,如果该线程没有调用这类方法,就算中断标志位被设置了,也不会抛出异常。

可以想象,在sleep、join、wait这些方法中,即使处于等待状态,也依然会去检测自身线程中断标志位状态,看看是否需要抛出异常。

2.2中断的意义

两个角度去看待:

  1. 中断可以唤醒一个线程:比如,一个处于wait状态的线程,通过中断,就可以让它重新苏醒过来。当然,醒来以后做什么事,由catch块来控制,然后,就继续一行一行往下执行代码罗~
  2. 可以通过中断来结束一个线程的任务。比如示例一:在一个runable内部有如下代码,如果让该线程interrupt,while循环就会被调出,也就结束了任务。
  3. 切换任务:如示例二,try块在while内部,此时在catch中做其他操作,其实可以切换工作,也可以做些别的。

示例一:

public void run() {
 try {
          while (true){
                Thread.sleep(1000);
                System.out.println("b");
          }
          } catch (InterruptedException e) {
                e.printStackTrace();
          }
}

示例二:

public void run() {
          while (true){
              try {
                 Thread.sleep(1000);
                 System.out.println("b");
               } catch (InterruptedException e) {
                 doOtherWork();
               }
          }    
}

2.3如何判断线程是否被中断

用Thread.currentThread().isInterrupted()啦;
有个高仿的方法,叫做Thread.interrupted(),长得差不多,其实,它是重置标志位用的。

来看下源码:

public static boolean interrupted() {
        //返回标志位,并重置标志位,第二次必返回false
        return currentThread().isInterrupted(true);
}

public boolean isInterrupted() {
        return isInterrupted(false);
}

源码长得也差不多,都调用了一个native方法isInterrupted(),isInterrupted只有一个参数ClearInterrupted,看名字就能get到——是否清除标志位。

2.4中断标志位什么时候清除

  1. 调用interrupted()后;
  2. 线程被interrupt,抛出异常之前,标志位就会被清除,所以,抛出异常后,若调用isInterrupted()来查询,会返回false;

2.5什么时候中断无效

当线程处于synchronized获取锁的过程中,是不会抛出异常的,中断无效。若此时死锁了,就永远也无法抛出了。

2.6一些使用场景

  1. 点击某个桌面应用中的取消按钮时;
  2. 某个操作超过了一定的执行时间限制需要中止时;
  3. 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
  4. 一组线程中的一个或多个出现错误导致整组都无法继续时;
  5. 当一个应用或服务需要停止时。

(使用场景参考了http://www.infoq.com/cn/articles/java-interrupt-mechanism )

3 暂停、继续、停止

suspend(),暂停
resume(),恢复线程、
stop(),停止线程

三个方法均已经过时了。原因是,suspend并不会释放线程所占有的资源,而是占着资源进入睡眠状态,容易发生死锁;而stop方法则直接一刀切,杀死线程,完全没有释放资源的机会。
其中,暂停、继续、可以用等待、通知机制来替代。

那么,如何停止线程呢?标准做法是:可以通过中断、或者是设置一个标志位来停止线程。

private static class stopThread extends  Thread{
        private boolean isRunning = true;

        @Override
        public void run() {
            while(isRunning && !Thread.currentThread().isInterrupted()){
                System.out.println("i am running~~~");
            }
        }

        public void stopThread(){
            this.isRunning = false;
        }

    }