Java线程和并发 - Threads and Runnables
每个Java程序都有一个默认的main线程,它执行main()方法。程序也能增加线程在后台执行时间密集型任务。这样可以对用户作出响应。这些线程执行的代码封装在runnables内。
每个线程否有自己的堆栈,防止线程互相干扰。单独的堆栈让线程跟踪自己的指令,还为线程提供了它自己的方法参数、局部变量和返回值的副本。
Thread和Runnable
Thread类为底层的操作系统线程架构提供了统一的接口。一个操作系统的线程对应一个Thread对象。Runnable接口提供线程要执行的代码-代码位于run()方法内,它没有参数也没有返回值,但是,它可能抛异常。
增加Thread和Runnable对象
除了默认的main线程,其他的都由Thread和Runnable对象增加。Thread有几个构造器,其中一些需要Runnable参数。
有两种办法增加Runnable对象。第一个办法是增加一个实现Runnable的匿名类:
Runnable r = new Runnable() {
@Override
public void run() {
// perform some work
System.out.println("Hello from thread");
}
};
Java 8开始,可以使用lambda表达式(传给构造器或者方法的匿名函数):
Runnable r = () -> System.out.println("Hello from thread");
增加Runnable对象之后,可以传给Thread的构造器。比如:
Thread t = new Thread(r);
一些构造器不接受Runnable参数。比如,Thread()。此时,你必须扩展Thread,覆盖它的run()方法,来提供要执行的代码:
class MyThread extends Thread
{
@Override
public void run()
{
// perform some work
System.out.println("Hello from thread");
}
}
// ...
MyThread mt = new MyThread();
Thread State
线程对象有关联的状态-名字、指示线程alive或者dead、执行状态(在运行吗)、优先级和是否daemon。
每个线程都有name,默认的前缀是“Thread-”。可以在构造器中设置name,也可以使用setName方法。
Thread t1 = new Thread(r, "thread t1");
System.out.println(t1.getName()); // Output: thread t1
Thread t2 = new Thread(r);
t2.setName("thread t2");
System.out.println(t2.getName()); // Output: thread t2
调用isAlive()方法,如果返回true,说明线程还活着,否则返回false。线程的寿命从实际上执行start()时开始,到离开run()方法为止。
Thread t = new Thread(r);
System.out.println(t.isAlive()); // Output: false
线程的执行状态见Thread.State,包括:
- NEW:线程还没有启动
- RUNNABLE:线程在执行
- BLOCKED:线程被阻塞,等待一个监视器锁(monitor lock )
- WAITING:线程无限期地等待另一个线程执行特定的动作
- TIMED_WAITING:线程在一定时间内等待另一个线程执行特定的动作
- TERMINATED:线程已经退出
Thread t = new Thread(r);
System.out.println(t.getState()); // Output: NEW
当计算机有足够的处理器的时候,操作系统会为每个线程分配处理器。当处理器不够的时候,线程必须切换来共享处理器。
操作系统使用scheduler决定一个等待的线程何时执行。
- Linux 2.6 到 2.6.23 使用O(1) Scheduler
- 2.6.23以后,默认调度器是Completely Fair Scheduler
一个多级反馈队列(multilevel feedback queue)和许多其他线程调度器会考虑priority(线程的相对重要性)。他们经常结合抢占式调度(高优先级的线程抢占)和循环调度(相同优先级的线程有相同的时间片)。
getPriority()方法可以返回当前优先级,setPriority方法可以设置优先级。优先级范围从Thread.MIN_PRIORITY到Thread.MAX_PRIORITY,默认优先级是Thread.NORMAL_PRIORITY。
Thread t = new Thread(r);
System.out.println(t.getPriority());
t.setPriority(Thread.MIN_PRIORITY);
线程可以分为守护线程或者非守护线程-守护线程在一个程序的最后一个非守护线程死亡后死亡。
Thread t = new Thread(r);
System.out.println(t.isDaemon()); // Output: false
默认地,线程是非守护线程。想增加守护线程,可以调用setDaemon方法:
Thread t = new Thread(r);
t.setDaemon(true);
当非守护的默认main线程结束,而后台还有其他非守护线程时,程序并不会结束。而如果后台线程都是守护线程,那么默认的main线程结束,程序也立刻退出了。
调用start()方法会执行该线程。如果线程已经启动了正在运行,或者已经死亡了,会抛出java.lang.IllegalThreadStateException。
Thread t = new Thread(r);
t.start();
调用start()方法,导致运行时增加一个底层线程,调度它执行run()方法中的代码。start()返回之前,不会等待它的任务的完成。执行完run()方法,线程被销毁。
执行线程的高级任务
interruption
线程有中断机制-一个线程能中断另一个线程。当线程被中断的时候,会抛InterruptedException。该机制由三个方法组成:
- void interrupt():终端调用该方法的线程。当一个线程因为sleep()或者join()阻塞的时候,线程的interrupted状态被清除,并且抛出InterruptedException。否则,设置中断状态,执行一些其他操作
- static boolean interrupted():测试当前线程是否被中断了。线程的中断状态由本方法清除
- boolean isInterrupted():测试线程是否被终端,该线程的中断状态不受影响
public class ThreadInterruptDemo {
public static void main(String[] args) {
Runnable r = () -> {
String name = Thread.currentThread().getName();
int count = 0;
//如果不被中断就一直执行
while (!Thread.interrupted())
System.out.println(name + ": " + count++);
};
Thread thdA = new Thread(r);
Thread thdB = new Thread(r);
thdA.start();
thdB.start();
while (true) {
double n = Math.random();
//main线程不至于马上退出
if (n >= 0.49999999 && n <= 0.50000001)
break;
}
//main线程让后台线程中断,程序结束
thdA.interrupt();
thdB.interrupt();
}
}
如果你运行上面的程序,会发现Thread-0和Thread-1交替输出一些信息后结束。
Joining
一个线程(例如main线程)有时候会启动另一个线程执行一个冗长的计算,下载大文件,或者执行其他费时间的任务。完成它的其他任务后,启动了工作线程的线程已经做好处理工作线程结果的准备,等待工作线程完成或者死亡。
Thread类提供了三个join()方法,允许当前线程等待join()方法被调用的线程的死亡:
- void join():无限期地等待线程的死亡。当任何线程中断了当前线程,抛InterruptedException。如果异常被抛出,interrupted状态被清除
- void join(long millis):最多花millis毫秒的时间,等待线程的死亡。0表示无限期等待。如果millis是负的,抛IllegalArgumentException。当任何线程中断了当前线程,抛InterruptedException。。如果异常被抛出,interrupted状态被清除
- void join(long millis, int nanos):最多等millis毫秒和nanos纳秒。如果nanos是负的,或者大于999999抛IllegalArgumentException。当任何线程中断了当前线程,抛InterruptedException。如果异常被抛出,interrupted状态被清除
public class ThreadPiDemo {
// pi计算使用的常量
private static final BigDecimal FOUR = BigDecimal.valueOf(4);
// rounding 模式
private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN;
private static BigDecimal result;
public static void main(String[] args) {
Runnable r = () -> result = computePi(50000);
//工作线程
Thread t = new Thread(r);
t.start();
try {
//main线程等待工作线程的结束
t.join();
} catch (InterruptedException ie) {
// 不会到达这里,因为不调用interrupt()
}
//工作线程结束以后,main线程打印输出,程序结束
System.out.println(result);
}
static BigDecimal computePi(int digits) {
int scale = digits + 5;
BigDecimal arctan1_5 = arctan(5, scale);
BigDecimal arctan1_239 = arctan(239, scale);
BigDecimal pi = arctan1_5.multiply(FOUR).
subtract(arctan1_239).multiply(FOUR);
return pi.setScale(digits, BigDecimal.ROUND_HALF_UP);
}
static BigDecimal arctan(int inverseX, int scale) {
BigDecimal result, numer, term;
BigDecimal invX = BigDecimal.valueOf(inverseX);
BigDecimal invX2 = BigDecimal.valueOf(inverseX * inverseX);
numer = BigDecimal.ONE.divide(invX, scale, roundingMode);
result = numer;
int i = 1;
do {
numer = numer.divide(invX2, scale, roundingMode);
int denom = 2 * i + 1;
term = numer.divide(BigDecimal.valueOf(denom), scale, roundingMode);
if ((i % 2) != 0)
result = result.subtract(term);
else
result = result.add(term);
i++;
}
while (term.compareTo(BigDecimal.ZERO) != 0);
return result;
}
}
Sleeping
Thread类的静态方法sleep,让线程休眠:
- void sleep(long millis):休眠millis毫秒。实际休眠时间由系统计时器和调度器的精度和准确性决定。如果millis是负的,抛IllegalArgumentException。如果任何线程中断了当前线程,抛InterruptedException。如果异常被抛出,当前线程的interrupted被清除
- void sleep(long millis, int nanos):休眠millis毫秒和nanos纳秒
sleep()比忙循环好,不会浪费处理器周期。
public class ThreadSleepDemo {
public static void main(String[] args) {
Runnable r = () -> {
String name = Thread.currentThread().getName();
int count = 0;
//如果不被中断就一直执行
while (!Thread.interrupted())
System.out.println(name + ": " + count++);
};
Thread thdA = new Thread(r);
Thread thdB = new Thread(r);
thdA.start();
thdB.start();
try {
//main线程不至于马上退出
Thread.sleep(2000);
} catch (InterruptedException ie) {
}
//main线程让后台线程中断,程序结束
thdA.interrupt();
thdB.interrupt();
}
}