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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值