JAVA-并发编程(三)
sschrodinger
2019/5/16
线程
线程是现代操作系统的最小调度单位,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量。
线程状态
Java 线程在运行的生命周期中可能处于以下的 6 种不同状态,在给定的一个时刻,线程只能处于其中一个状态
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断) |
TIMED_WAITING | 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |
note
- 阻塞状态是线程在进入 synchronized 关键字修饰的的方法或代码块(获取锁)时的状态,但是阻塞在 java concurrnet 包中 Lock 接口的线程状态却是等待状态,因为 java.cincurrent 包中 Lock 接口对于阻塞的实现均使用了 LockSupport 类中相关的方法。
Daemon 线程
Daemon 线程是一种支持型线程,他主要被用作程序中后台调度以及支持性的工作。
note
- 当一个 Java 虚拟机中不存在非 Daemon 线程的时候,java 虚拟机会自动退出。
- 在构建 Daemon 线程时,不能依靠 finally 块中的内容来确保执行关闭或清理资源的逻辑,因为 Daemon 线程的 finally 语句块不一定一定被执行。
启动和终止线程
构造线程
在程序运行之前需要构造一个线程对象,线程对象在构造的时候需要提供线程所需要的属性,如线程所属的线程组、线程优先级、是否是 Daemon 线程等信息。
下面是 java 线程初始化的部分源码。
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
//当前线程就是父线程
Thread parent = currentThread();
this.group = g;
//将Daemon、priority属性设置为父线程的对应属性
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
this.name = name.toCharArray();
this.target = target;
setPriority(priority);
//将父线程的InheritableThreadLocal复制过来
if (parent.inheritableThreadLocals != null) {
this.inheritableThreadLocals = TheadLocal.createInheritedMap(parent.inheritableThreadLocals)
}
//分配一个线程ID
tid = nextThreadID();
}
启动线程
调用 start()
方法。
中断
与操作系统的中断不同,java 的中断只是一个标志位,而不是去真正中断一个线程。
中断可以理解为线程的一个标志位属性,他表示一个运行中的线程是否被其他线程进行了中断操作(标志位置位)。其他线程调用该线程的 interrupt()
函数来对该线程进行置位。
线程通过检查自身是否被中断来进行响应,线程通过 isInterrupt()
来进行判断是否被中断。也可以调用静态方法 Thread.interrupt()
对当前线程的中断标识位进行复位。如果该线程已经处于终结状态,即是该线程被中断过,isInterrupt()
也会返回 false 。
许多申明抛出 InterruptException
的方法,在抛出 InterruptException
之前,Java 虚拟机会先将该线程的中断标识位清除,然后抛出 InterruptException
,此时调用 isInterrpted()
方法也会返回 false。
下面是 Interrupt 的例子:
public class Interrupted {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
sleepThread.setDaemon(true);
Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
busyThread.setDaemon(true);
sleepThread.start();
busyThread.start();
TimeUnit.SECONDS.sleep(5);
sleepThread.interrupt();
busyThread.interrupt();
System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
SleepUtils.second(2);
}
static class SleepRunner implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
SleepUtils.second(10);
}
}
}
static class BusyRunner implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
}
}
}
}
//output:
//SleepThread interrupted is false
//BusyThread interrupted is true
suspend()、resume()、stop()
这是三个 java 不推荐的方法去暂停,恢复和停止线程。
note
- 以suspend()为例,在调用后,县城不会释放已经占有的资源(比如锁),而是占着资源进入睡眠状态,这样容易引发死锁问题。
- 同样,stop()方法在终结一个线程时不会保证线程的资源正常的释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序而可能工作在不确定状态下。
- 暂停和恢复操作可以使用等待/通知机制来代替
安全的终止线程
利用中断或者一个boolean变量可以很方便的取消或者停止任务。
实例代码如下:
package test;
import java.util.concurrent.TimeUnit;
public class Shutdown {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Runner one = new Runner();
Thread countThread = new Thread(one, "CountThread");
countThread.start();
TimeUnit.SECONDS.sleep(1);
countThread.interrupt();
Runner two = new Runner();
countThread = new Thread(two, "CountThread");
countThread.start();
TimeUnit.SECONDS.sleep(1);
two.cancel();
}
private static class Runner implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
// TODO Auto-generated method stub
while (on && !Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("count i = " + i);
};
public void cancel() {
on = false;
}
}
}
两种方式都是利用标志位跳出了while循环,以达到取消线程的任务。
Thread.join()
如果线程A执行了 thread.join()
语句,其含义是:当前线程A等待thread线程终止后才从 thread.join)()
返回。同时还提供了 thread.join(long)
和 thread.join(long millis, int nanos)
两个超时方法。
如下代码是join的实例。
package test;
import java.util.concurrent.TimeUnit;
public class Join {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Thread previous = Thread.currentThread();
for (int i = 0; i < 10; i++) {
//每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能够从等待中返回
Thread thread = new Thread(new Domino(previous), String.valueOf(i));
thread.start();
previous = thread;
}
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "\tterminate");
}
static class Domino implements Runnable {
private Thread thread;
public Domino(Thread thread) {
// TODO Auto-generated constructor stub
this.thread = thread;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
thread.join();
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + "\tterminate");
}
}
}
//output:
//main terminate
//0 terminate
//1 terminate
//2 terminate
//3 terminate
//4 terminate
//5 terminate
//6 terminate
//7 terminate
//8 terminate
//9 terminate
实际上,join机制是利用了经典的等待/通知机制,现摘要部分源码如下:
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
//调用thread.join()会调用此方法
//就是经典的步骤
//获取对象的锁
//如果条件不满足,那么调用对象的wait()方法,返回之后仍要检查条件
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}