Java-并发.01.线程基础 & API

线程6种状态

  1. New, 创建 Thread 实例之后;
  2. Runnable, 执行 thread.start() 之后;
  3. Blocked, 线程试图获取 ReentrantLock 失败, 或进入 synchronize 代码块, 或调用 Block IO;
  4. Waiting, 调用 object.wait()thread.join() 之后;
    • 调用 object.wait(), condition.await() 方法都会产生 WAITING 状态;
    • 调用 thread.join(long millis) 后, 调用者会 Waiting 一直到 thread 线程退出;
  5. Time-Waitting, 调用 thread.join(long millis)thread.sleep(long millis)、或者 Object.wait(long)lock.tryLock(long) 时;
  6. Terminated: 线程的 run() 方法正常退出或者 run() 方法抛出未捕获异常时;

上面的状态来自 Oracle JDK 8 java.lang.Thread.State, 并不等同于 unix 下的原生线程状态,
Thread.State (Java Platform SE 8 )

图-线程6种状态的转换:
Thread States

线程控制 API

start

// 第一种Runnable接口
Thread t = new Thread(new runnable() {
public void run() {
try{
while (true) {
Thread.sleep(1000);
}
} catch (InterruptedException e) {
} finally {
}
}
});
// new之后线程处于"New"状态
t.start(); // start之后线程处于"Runnable"状态

// 第二种
Class MyThread extends Thread {
public void run(){
// doSomething
}
}
new Mythread().start();

interrupt

  • 调用 t.interrupt() 方法时, 线程 t 会收到中断信号, Java 并没有要求线程一定响应中断. 线程应该根据情况决定是否响应中断, 循环调用 t.isInterrupted() 可以检测线程的中断标志位.
  • 如果线程内调用了 sleep() 或者 wait() 方法让线程进入等待状态, 当调用 t.interrupt(), 线程会抛出 InterruptException, 如果你的线程里调用了可能抛出该异常的阻塞方法, 那么就不必每次调用 isInterrupt() 检测中断状态了, 在 catch 里捕获该异常即可.
  • 如果线程已经被中断的情况下再调用 sleep(), sleep() 方法会清除中断状态并且抛出上述异常, 并不会进入 sleep 状态, 所以线程循环中有 sleep() 的也不必用 isInterrupt 检查中断状态
  • 可抛出中断异常的: 线程内调用 wait(), 或者调用 thread.join()thread.sleep()
    Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
    try {
    while(!Thread.currentThread().isInterrupted() /* && */) {
    Thread.sleep(5000); // 如果有sleep, 上面的isInterrupted不必要
    }
    } catch (InterruptedException e) {}
    }
    });

    t.start(); // sub-thread now is "runnnable"

    t.interrupt(); // main thread interrupt sub-thread

join

执行 thread.join() 的线程会进入 waiting 状态, 直到 thread 线程终止或自然退出, 继续执行后面的代码

MyThread thread = new MyThread();
thread.start();
thread.join(); // 在这里waiting

// 上面的thread退出后, 才会进行到这里

sleep

执行 thread.sleep(m) 的线程会进入 timed_waitting 状态 m 毫秒(注意, 并没有 sleep 这种状态),
Thread.sleep() 与线程调度器交互,它将当前线程设置为等待一段时间的状态。一旦等待时间结束,线程状态就会被改为可运行(runnable),并开始等待 CPU 来执行后续的任务。因此,当前线程的实际休眠时间取决于线程调度器,而线程调度器则是由操作系统来进行管理的。

sleep 和 wait 的区别?

比较 thread.sleep(long millis)object.wait():

  • Thread.sleep() 方法是一个静态方法,作用在当前线程上,线程进入 timed_waiting 状态;
  • obj.wait() 方法是一个实例方法,调用 obj.wait() 的线程,会进入 waiting 状态;
  • sleep() 方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是 cpu 对线程的监控状态依然保持者,当指定的时间到了又会自动恢复 runnable。
  • 线程 A 里调用 obj.wait() 方法的时候,线程 A 进入等待此对象的等待队列,放弃对象锁并进入 waiting 状态,只有针对此对象调用 notify() 方法后, 线程 A 才会从对象锁的等待队列中被取出。

其他不同,主要是锁的状态:

  • 调用 wait() 方法时,线程在等待的时候会释放掉它所获得的 monitor,但是调用 Thread.sleep() 方法时,线程在等待的时候仍然会持有 monitor 或者锁。
  • Java 中的 wait() 方法应在同步代码块中调用(已经获得了对象锁的情况下, 调用 obj.wait() 会放弃锁)

总结:

  • 如果你需要暂定线程一段特定的时间就使用 sleep() 方法,如果你想要实现线程间通信就使用 wait() 方法。

如何终止线程?

几个问题:

  1. 被调用了 sleep() 的线程(timed_waiting 状态)可以被 interrupt() 抛出异常吗?
  2. 调用了 thread.join() 的线程(waiting 状态)可以被 interrupt() 抛出异常吗?
  3. 调用了 object.wait() 的线程(waiting 状态)可以被 interrupt() 抛出异常吗?
  4. 调用阻塞 IO 方法被阻塞住的线程可以被 interrupt() 抛出异常吗?
  5. 试图抢占锁(synchronized 或 ReentrantLock)但失败的线程(blocked 状态)可以被 interrupt() 抛出异常吗?

答案: 可以, 可以, 可以, 否, 否

只有处于waiting 或 timed_waiting 状态的线程才可以抛出 InterruptException 异常被中断, block 状态的线程不可以;

1)如何终止 waitingtimed_waiting 状态的线程呢? 有两种方式:

  • 用 volatile 的标志位控制线程的循环逻辑;
  • thread.interrupt(): 中断当前线程, 线程的循环里应该 try-catch InterruptException 并处理
  • thread.stop(): 不推荐

2)但是对于进入 blocked 状态的线程, 是无法被 interrupt() 中断的, 所以可能的做法是: 关闭阻塞的资源

class IOBlocked implements Runnable {
private InputStream in;
public IOBlocked(InputStream is) {
in = is;
}
@Override
public void run() {
try {
in.read();
} catch (IOException e) {
} catch (InterruptException e1) {
// 事实永远无法到达这, 因为read不抛InterruptException
// InterruptException是受检异常
}
}
}

public class HowToInterruptIOBlockedThreads {
public static void main(String[] args) throws Exception {

// 创建线程池
ExecutorService service = Executors.newCachedThreadPool();

// 打开网络流
ServerSocket server = new ServerSocket(8080);
InputStream stream = new Socket("localhost", 8080).getInputStream();

// 执行会导致IO Blocked的线程
service.execute(new IOBlocked(stream));

// 主线程sleep
TimeUnit.MILLISECONDS.sleep(100);

//尝试停止所有正在执行的任务, shutdownNow会尝试调用所有线程的interrupt
service.shutdownNow();

//通过关闭线程操作的资源来释放阻塞的线程
stream.close();
}
}

唤醒线程

如何唤醒 sleep / wait / blocked 的线程?

  • 对于因调用 object.wait() 进入 waiting 状态的线程,调用 object.signal()
  • 对于因调用 t.sleep(), t.join() 而进入 timed_waiting 状态的线程, 调用 t.interrupt() 可以线程抛出 InterruptedException 来达到”唤醒”的效果;
  • 对于因为 IO 阻塞而进入的 blocked 状态的线程, 没有办法唤醒;

被弃用的方法

Thread 类不再推荐被使用的方法: ~yield,stop,suspend,resume~

yield

yield 方法会临时暂停当前正在执行的线程,来让有同样优先级的正在等待的线程有机会执行。
如果没有正在等待的线程,或者所有正在等待的线程的优先级都比较低,那么该线程会继续运行。
执行了 yield 方法的线程什么时候会继续运行由线程调度器来决定。
yield 方法不保证当前的线程会暂停或者停止,但是可以保证当前线程在调用 yield 方法时会放弃 CPU。

yield()应该做的是让当前运行线程回到 可运行状态(Runnable),以允许具有相同优先级的其他线程获得运行机会。
因此,使用 yield()的目的是让相同优先级的线程之间能适当的轮转执行。
但是,实际中无法保证 yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

stop

该方法天生是不安全的。使用 thread.stop()停止一个线程,导致释放(解锁)所有该线程已经锁定的监视器(因沿堆栈向上传播的未检查 ERROR—— ThreadDeath 而解锁)。 // → 非受检异常
如果之前受这些监视器保护的任何对象处于不一致状态,则不一致状态的对象(受损对象)将对其他线程可见,这可能导致任意的行为。
ThreadDeath 是 java.lang.Error,不是 java.lang.Exception。不受检异常意味着我们不会有意识在代码里写 Try-Catch 去处理异常, 比如在 finally 里释放锁

上面的意思是:
线程当前可能持有一个监视器(或锁),执行 thread.stop() 将会产生一个 ThreadDeath 错误(是一种不受检 ERROR),线程向上抛出错误,导致监视器被解锁。
可能导致的问题: 以银行转账的例子, 如果在”减扣 A 余额, 增加 B 余额”的过程中, 线程被 stop, 将产生业务数据的不一致.

建议 用 interrupt 替代 stop, 在线程中循环检测 thread.isInterrupted() 或者捕获 InterruptException 然后由业务代码进行收尾处理.

ThreadDeath 和 InterruptException 的区别是:

  • 前者不受检, 意味着业务代码没有机会捕获并处理, 会向上层堆栈抛出错误, 线程状态变为 “Terminated”;
  • 后者是受检异常, 可以被捕获并由业务代码处理;

suspend & resume

  • 当某个线程的 suspend()方法被调用时,该线程会被挂起。如果该线程占有了锁,则它不会释放锁。线程在挂起的状态下还持有锁,这导致其他线程将不能访问该资源直到目标线程恢复工作。
  • 线程的 resume()方法 会恢复因 suspend() 方法挂起的线程,使之重新能够获得 CPU 执行。

建议使用 object.waitobject.notify 方法代替 suspend & resume

线程属性

优先级

  • java 中线程优先级范围 MIN_PRIORITY~MAX_PRIORITY (其值1~10), NORMAL_PRIORITY (其值=5);
  • 线程默认情况下继承父线程的优先级;

daemon

thread.setDaemon(true);
thread.start();

当 JVM 还存在一个非守护线程, JVM 就不会退出, 当存活的线程仅剩下守护线程时, JVM 才会退出.
守护线程最典型的应用就是 GC

异常处理器

thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
}
});
thread.start();