线程

目录

HelloWorld

在 Java 中有两种方式创建线程,第一种方式是实现 Runnable 接口,第二种方式是继承 Thread 类,下面是使用示例:

public class RunnableDemo implements Runnable{
    @Override
    public void run() {
        System.out.println("I am in runnable Demo");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new RunnableDemo());
        thread.start();
    }
}
public class ThreadDemo extends Thread{
    @Override
    public void run() {
        System.out.println("I am in Thread Demo");
    }

    public static void main(String[] args) {
        Thread thread = new ThreadDemo();
        thread.start();
    }
}

如果启动线程,需要调用 start 方法,直接运行 run 不会启动新的线程。

线程状态

在线程执行期间,会经历多种状态,下面是一个示意图:

图片来自 Java 并发编程的艺术

下面是线程生命周期对应的几种状态

  • New - 初始状态,线程刚被创建,但是还没有调用 start 方法
  • Runnable(Ready to run) - 可运行状态,其他线程调用了该线程的 start 方法。该状态的位于可运行的线程池中,等待被线程调度选中,获得 CPU 的使用权。
  • Running - 运行状态,Runnable 线程获得了 CPU 使用权,执行程序代码。
  • Blocked - 阻塞状态,表示线程被锁阻塞。例如进入 synchronized 同步块时等待锁。
  • Waiting - 等待状态,处于该状态的线程一个是调用了下面3个方法:Object.wait, Thread.join 或者 LockSupport.park。处于 Waiting 状态的线程需要等待其他线程做出一些特定行为,例如通过调用 Object.wait() 等待的线程需要另外一个线程执行 Object.notify() 或者 Object.notifyAll() 来唤醒。
  • Time Waiting - 超时等待,当等待时间超过指定时间后会自行返回。
  • Terminated - 终止状态,表示线程已经执行完毕

在 JDK 中定义了线程的六种状态,位于 Thread.State 中,下面是 State 的定义:

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

获得线程状态

Thread 类提供了 getState 方法来获得线程状态,下面是一个使用示例:

public class StateDemo extends Thread {

    @Override
    public void run() {
        System.out.printf("%-18s %s\n","in run method:", getState());
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new StateDemo();
        System.out.printf("%-18s %s\n","init:",  thread.getState());
        thread.run();
        System.out.printf("%-18s %s\n", "after run:", thread.getState());
        thread.start();
        System.out.printf("%-18s %s\n","after start:", thread.getState());
        Thread.sleep(1);
        System.out.printf("%-18s %s\n","finish:", thread.getState());
    }
}

线程优先级

在 Java 标准中,可以通过设置优先级来调整线程获得时间片的优先程度。Java 中定义的线程优先级的范围是1~10,默认优先级是 5,下面是 Thread 类中关于几个变量的定义:

/**
 * The minimum priority that a thread can have.
 */
public final static int MIN_PRIORITY = 1;

/**
 * The default priority that is assigned to a thread.
 */
public final static int NORM_PRIORITY = 5;

/**
 * The maximum priority that a thread can have.
 */
public final static int MAX_PRIORITY = 10;

通过 setPriority 方法可以设置线程的优先级。原则上优先级高的线程会优先获得执行时间,但是并不能保证一定会这样,而且在不同的 JVM 以及操作系统中,对线程优先级的划分和优先级的支持程度是不一样,有些操作系统有可能会忽略优先级的设定。下面是一个使用线程优先级的示例:

public class PriorityDemo {

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "(" + Thread.currentThread().getPriority() + ")"
                            + ", loop " + i);
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "(" + Thread.currentThread().getPriority() + ")"
                            + ", loop " + i);
                }
            }
        });

        thread.setPriority(1);
        thread2.setPriority(10);
        thread.start();
        thread2.start();
    }
}

输出结果为:

Thread-0(1), loop 0
Thread-0(1), loop 1
Thread-0(1), loop 2
Thread-0(1), loop 3
Thread-0(1), loop 4
Thread-0(1), loop 5
Thread-0(1), loop 6
Thread-0(1), loop 7
Thread-0(1), loop 8
Thread-0(1), loop 9
Thread-1(10), loop 0
Thread-1(10), loop 1
Thread-1(10), loop 2
Thread-1(10), loop 3
Thread-1(10), loop 4
Thread-1(10), loop 5
Thread-1(10), loop 6
Thread-1(10), loop 7
Thread-1(10), loop 8
Thread-1(10), loop 9

从结果可以看到,优先级并没有起到什么作用。所以程序的正确性不能依赖线程的优先级高低。

守护线程

守护 (Daemon) 线程是一种支持型线程,主要用作后天的调度以及支持性工作。虚拟机的垃圾回收线程就是守护线程。当 Java 虚拟机中不存在非守护线程时,Java 虚拟机就会退出。通过 setDaemon 方法设置守护线程。需要注意的一点是当 Java 虚拟机退出守护线程时,finally 块并不一定会执行。下面是一个使用示例:

public class DaemonDemo {
    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("run finally in daemon thread.");
                }
            }
        });

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

运行上面的程序,不会输出 run finally in daemon thread.,因为主线程结束时,TimeUnit.SECONDS.sleep(1); 还没执行完。所以不要用守护线程来完成资源回收的工作。关于守护线程具体有什么应用,可以参考下面的回答:
http://stackoverflow.com/questions/7067578/when-are-daemon-threads-useful

睡眠

Thread 中 sleep 方法会使线程进入睡眠状态,从而让出 CPU 时间,供其它线程使用。sleep 是一个静态方法,只会使当前线程进入睡眠状态。同时线程在睡眠时不会让出拥有的对象锁。它的主要作用就是为了不让当前线程霸占进程所获取的 CPU 资源,给其他的线程留出执行的时间。sleep方法有两个重载的形式,一种是结果一个毫秒时间 ( millisecond),一个是接受一个毫秒 + 纳秒时间(nanosecond),如下所示:

  public static native void sleep(long millis) throws InterruptedException;

  public static void sleep(long millis, int nanos) throws InterruptedException {
       if (millis < 0) {
           throw new IllegalArgumentException("timeout value is negative");
       }

       if (nanos < 0 || nanos > 999999) {
           throw new IllegalArgumentException(
                               "nanosecond timeout value out of range");
       }

       if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
           millis++;
       }

       sleep(millis);
   }

从上面的代码中我们可以看到,对于 sleep(long millis, int nanos) 方法,线程最终还是按照毫秒睡眠的,只是超过纳秒时间超过 0.5 毫秒,将睡眠的毫秒时间加 1。看上去这个方面并没有作用,那么为什么还要定义这个方法呢,具体的可以参考这里 What's the purpose of sleep(long millis, int nanos)?,大体意思就是,有些系统支持纳秒级睡眠的操作,并且纳秒级睡眠对该系统比较重要。但是有些系统比如 Windows 并不支持纳秒级睡眠,所以为了兼容不同的系统,就定义了该方法,对于不支持纳秒睡眠的系统,用毫秒来代替。还要注意的一点就是这里传入的睡眠时间是线程的最少的睡眠时间,到达了指定的睡眠时间后,线程有可能无法获得 CPU 时间,因此就无法立即执行。如果对睡眠的线程调用 interrupt 方法,会使线程抛出 InterruptedException 异常而终止睡眠状态。下面是一个示例:

public class SleepDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for(int i = 0; i < 10; i++) {
                        Thread.sleep(1000);
                        System.out.printf("I sleep %d second.\n", i + 1);

                    }
                } catch (InterruptedException e) {
                    System.out.println("I am interrupted.");
                    e.printStackTrace();
                }
            }
        });

        thread.start();
        try {
            TimeUnit.SECONDS.sleep(5);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

中断

Java 中的中断机制是一种协作机制,通过中断并不能直接终止另外一个线程,而需要被中断的线程自己处理中断。运行面的代码,程序会一直执行下去,即便在主线程里对 demo 线程执行了中断操作,demo 线程还是会继续执行。下面的代码中 demo.interrupt() 操作只是将 demo 线程的中断状态设为 true,并没有实质性的操作。

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread demo = new Thread(new Runnable() {
            @Override
            public void run() {
                int threshold = 10000;
                int index = 1;
                while (true) {
                    if(index++ % threshold == 0) {
                        System.out.println(index);
                        index = 1;
                    }
                }
            }
        });

        demo.start();
        TimeUnit.SECONDS.sleep(1);
        demo.interrupt();
    }
}

每个线程中都有一个与中断相关联的 boolean 属性,用来表示线程的中断状态(interrupt status)。中断状态初始时为 false。当另外一个线程通过调用 interrupt 方法中断一个线程时,会出现以下两种情况:

  • 如果被中断线程正在执行一个低级可中断的阻塞方法,例如 Thread.sleep()Thread.join() 或者 Object.wait(),那么它将取消阻塞并且抛出 InterruptedException
  • 如果线程没有执行上面的方法,那么 interrupt 只是将该线程的中断状态设置为 true。在被中断的线程里可以通过轮询中断状态,查看它是否被请求正在停止做的事。

中断状态可以通过 isInterrupted() 方法来读取,该方法只会读取中断状态。还有一个静态方法 -- Thread.interrupted(),该方法会首先读取中断状态,然后清除当前的中断状态(中断状态重设为 false)。下面是两种方法的源码:

/**
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if the current thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see #isInterrupted()
     * @revised 6.0
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    /**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if this thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see     #interrupted()
     * @revised 6.0
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

处理中断

在前面提到,对于低级的阻塞方法,如果线程被设置中断,会跑出 InterruptedException,当线程抛出 InterruptedException 之后,线程的中断状态会被重置为 false,下面是一个示例:

public void testInterruptException() throws InterruptedException {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                System.out.println("Thread is interrupted, and current stage is: " + Thread.currentThread().isInterrupted());
            }
        }
    });
    thread.start();
    Thread.sleep(1000);
    thread.interrupt();
}

输出结果如下:

Thread is interrupted, and current stage is: false

对于会抛出 InterruptedException 的阻塞方法,可以使用下面几种策略处理:

将异常抛给上层调用方法

public void method() throws InterruptedException {}

在将异常抛出之前做一些清理工作

public void method() throws InterruptedException{
    try{
        value = 10;
    } catch(InterruptedException e) {
        value = 0;
        throw e;
    }
}

在本层方法处理
使用该方式需要注意的不要 生吞中断。在上面我们提到抛出 InterruptedException 异常之后,线程中断状态会被设置为 false,如果不做处理,那么这样上层的调用方法就无法准确的获得当前线程的中断状态(不管当前线程是否被中断,上层方法获得的线程状态总是 false,就类似于中断被下层方法“吞”了)。所以,我们在捕捉了异常之后,应该重新调用 interrupt() 设置中断状态:

public static void handleInterruptException() {
     Thread thread = new Thread(new Runnable() {
         @Override
         public void run() {
             try {
                 Thread.sleep(4000);
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
                 //e.printStackTrace();
             }
         }
     });
     thread.start();
     thread.interrupt();
}

对于一般的方法,我们可以使用轮询的方式来查看当前线程的中断状态,根据中断状态做出相应的处理:

public static void handleInterruptByHand() throws InterruptedException {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            int max = 10000000, index = 0;
            while (!Thread.currentThread().isInterrupted()) {
                index++;
                if (index > max) {
                    index = 0;
                    System.out.println("doing task......");
                }
            }
            System.out.println("finish task");

        }
    });
    thread.start();
    Thread.sleep(100);
    thread.interrupt();
}

使用场景

中断的使用场景大概有以下几个:

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

Wait and Notify

在 Java 中可以用 wait/notify 来实现进程间的通信,一个经典的例子就是生产者和消费者模型。与 wait/notify 相关的函数有下面5个,这 5 个函数都是 Object 类中的方法,所以线程 wait 或者 notify 时都会关联一个相关的对象。

public final native void wait(long timeout) throws InterruptedException;

public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
            "nanosecond timeout value out of range");
    }

    if (nanos > 0) {
        timeout++;
    }

    wait(timeout);
}

public final void wait() throws InterruptedException {
    wait(0);
}

public final native void notify();

public final native void notifyAll();

首先来说一下上述函数的功能:

  • wait() - 使线程处于针对于当前对象的等待状态。通过另外一个线程调用等待对象的 notify() 或者 notifyAll() 方法可以唤醒等待的线程。wait 方法会抛出 InterruptedException,也就是说如果对等待线程调用 interrupt() 方法,也会使线程退出等待状态。在线程调用某个对象的 wait 方法之前,需要首先获得该对象的锁。当线程处于等待时会释放掉该对象的锁。当等待线程被唤醒之后需要重新获得等待对象的锁。处于等待状态的线程不会再占用 CPU 资源。
  • wait(long) / wait(long, int) - 超时等待,当超过预设的的时间之后即使没有 notify 或者 interrupt,线程也会退出等待状态。
  • notify() - 唤醒一个针对当前对象等待的线程,如果有多个线程正在等待当前对象,会随机选择一个唤醒。
  • notigyAll() - 唤醒所有针对当前对象等待的线程。

为什么线程在调用对象的 wait 或者 notify 方法之前需要首先获得对象锁呢?个人觉得主要原因就是为了确保某一个时间内只有一个线程执行该对象的 wait 或者 notify 方法。假设线程 1 正在执行 a.wait()随后线程 2 执行 a.notify()(线程 1 还没完成等待工作),线程 1 和 2 都没有获得对象锁,那么问题来了,因为线程 1 还没有完成等待的操作,那么线程 2 也就不知道线程 1 在等待,也就不会唤醒线程 1,当线程 1 完成等待操作之后,线程 2 也早已完成唤醒操作,线程 1 就会一直处于等待状态。

在 Java 中每个对象都有一个内置的锁,被称为 intrinsic lock 或者 monitor lock,Java 的 API 文档中经常把这个内置锁简称为 monitor。这里将其称为对象锁。获得对象锁的途径有下面三种(直接从 Javadoc 中抄的):

  • By executing a synchronized instance method of that object.
  • By executing the body of a {@code synchronized} statement that synchronizes on the object.
  • For objects of type {@code Class,} by executing a synchronized static method of that class.

也就是下面三种方式:

// 该方法获得是当前对象的锁
public synchronized void method(){}

// 该方法获得的是 Class 对象的锁
public synchronized static void staticMethod(){}

// 该方法获得是 obj 对象的锁
synchronized (obj) {
    obj.notify();
}

所以我们看到在使用 wait 或者 notify 方法时,必定要和 synchronized 方法或者 synchronized 同步块相关联,诸如下面的形式:

synchronized(obj) {
    obj.wait();
}

public synchronized void method() throws InterruptedException {
    wait();
}

为了保证程序的正确性,我们也需要对象的修改做同步。如果同步对象修改和 wait/notify 之间有一定的关联,那么需要将这两个操作放到一个同步块中。例如生产者消费者模型中:生产者生产了商品需要唤醒消费者,就需要将生产商品和唤醒消费者的操作放到一个同步块中,如下面的形式。

// buffer 是共享变量
Queue <String> buffer = new LinkedList <String> ();

public void method() {
    synchronized(buffer) {
        buffer.add(data);
        notify();
    }
}

假设对两者分别同步,如下面的代码:

class BlockingQueue {
    Queue<String> buffer = new LinkedList<String>();

    public void give(String data) {
        synchronized (buffer) {
            buffer.add(data);
        }
        synchronized (buffer) {
            notify();
        }
    }

    public String take() throws InterruptedException {
        synchronized (buffer) {
            while (buffer.isEmpty())
                wait();
        }
        synchronized (buffer) {
            return buffer.remove();
        }
    }
}

对于上述代码,下面是有可能发生的情况:

  1. 一个消费者线程调用 take() 方法,发现 buffer 为空
  2. 在消费者线程调用 wait() 方法之前,生产者线程来了,它调用 give() 方法,生产东西放到了 buffer 里并调用 notify() 方法
  3. 此时消费者开始调用 wait() 方法,假设此后生产者线程都不再生产商品,即不在调用 give() 方法了,那么消费者线程将一直等待下去,即便有商品可用。

所以需要将共享变量的修改以及 wait/notify 调用放到同一个同步块中。

同时对于 wait 的条件判断,我们需要使用 while,而不是 if ,如下所示:

synchronized(obj) {
    while (condition) {
        wait();
    }
}

这是因为线程可能存在 spurious wakeup,也就是说即便没有 notify , interrupt 或者超时,线程也有可能从 wait 的状态中醒过来。所以需要用 while 循环来判断是否需要线程再次进入 wait 状态。

关于 wait 和 notify 方法为什么会定义在 Object 类里,可以参考 这篇文章

生产者消费者模式

下面是生产者消费者的一个简单示例

import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumer {

    public static class Storage {
        private final int MAX_ITEM = 5;
        private Queue<Integer> queue = new LinkedList<>();

        public synchronized void put(Integer item) {
            while (queue.size() >= MAX_ITEM) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            queue.add(item);
            System.out.println("produce product: " + item);
            notifyAll();
        }

        public synchronized Integer take() {
            Integer item = 0;
            while (queue.isEmpty()) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // slow down the consumer speed.
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            item = queue.poll();
            System.out.println("consume product: " + item);
            notifyAll();
            return item;
        }
    }

    public static class ProducerThread implements Runnable {
        private Storage storage;

        public ProducerThread(Storage storage) {
            this.storage = storage;
        }

        @Override
        public void run() {
            int num = 20;
            for (int i = 0; i < num; i++) {
                storage.put(i);
            }
        }
    }

    public static class ConsumerThread implements Runnable {

        private Storage storage;

        public ConsumerThread(Storage storage) {
            this.storage = storage;
        }

        @Override
        public void run() {
            int num = 20;
            for (int i = 0; i < num; i++) {
                storage.take();
            }
        }
    }

    public static void main(String[] args) {
        Storage storage = new Storage();
        Thread consumerThread = new Thread(new ConsumerThread(storage));
        Thread producerThread = new Thread(new ProducerThread(storage));
        consumerThread.start();
        producerThread.start();
    }
}

Thread 方法

在 Thread 类中定义了一些其他的方法,下面介绍一下这些方法。

init

在运行线程之前首先构造一个线程对象,线程对象在构造的时候需要提供线程所需要的属性,例如线程所属的线程组、线程优先级等信息。Thread 类通过一个私有的 init 方法,用来初始化线程,下面是代码:

    /**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name.toCharArray();
        // 当前正在运行的线程是新线程的父线程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

在上面的过程中,一个新构造的线程对象是由其 Parent 线程来进行空间分配的,而 Child 线程继承了父线程是否为 Daemon、优先级和加载资源的 contextClassLoader以及可继承的 ThreadLocal,同时还会分配一个唯一的线程 ID (nextThreadID 方法,是一个 synchronized 方法)来标识这个Child 线程

start

通过调用 start 方法来启动一个线程,需要注意的是直接运行 run 方法是不会启动新的线程的。下面是 start 的代码,我们看到 start 方法其实是调用了一个 native 方法来启动线程的。

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

join

如果线程 T 执行了 thread.join() 语句,那么线程 T 将等待 thread 线程执行完,再往下执行,下面是一个使用示例:

public class JoinDemo {
    public static class JoinThread implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("in JoinThread");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new JoinThread());
        thread.start();
        System.out.println("in main thread: before thread.join()");
        thread.join();
        System.out.println("in main thread: after thread.join()");
    }
}

下面是 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()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

public final synchronized void join(long millis, int nanos) throws InterruptedException {

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
            "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }

    join(millis);
}

public final void join() throws InterruptedException {
    join(0);
}

public final native boolean isAlive();

我们看到 join 实际上是调用了 wait 方法,wait 获得的对象锁就是 thread 对象,当 thread 执行完之后再唤醒 wait 方法(底层实现的)。

yield

yield 是 Thread 类中的一个静态方法。yield 方法会给调度器发送一个暗示 (hint),告诉调度器当前线程将要让出 CPU 资源,让其他同优先级或者高优先级的线程来使用 CPU。使用这个方法有几点需要注意的地方:

  • 调度器可以选择忽略 yield 发出的暗示。
  • 不同的系统以及不同的 Java 版本对于 yield 的实现方法不同,具体的可以参考 这篇文章。在 Windows 的高版本 JDK 中(>=1.6),yield 只是将当前线程从运行状态 (running)变为就绪状态(ready),当前线程还是有可能再次竞争到 CPU 资源,再次执行。在 Linux 的实现中,需要其他线程都获得了 CPU 资源执行(不一定执行完)之后,被 yield 的线程才可以再次获取 CPU 资源。
  • Java 中的线程优先级也是个不靠谱的东西。

和线程优先级一样,程序的正确性不要依赖于 yield 方法。一般来说,yield 只用于调试。下面是一个示例:

public class YieldDemo {

    public static class YieldThread implements Runnable {

        @Override
        public void run() {
            int num = 10;
            for (int i = 0; i < num; i++) {
                System.out.println("YieldThread: " + i);
                Thread.yield();
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new YieldThread());

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int num = 10;

                for (int i = 0; i < num; i++) {
                    System.out.println("CommonThread: " + i);
                }
            }
        });

        thread.start();
        thread2.start();
    }
}

currentThread

通过 currentThread 方法可以获得当前正在运行的线程,下面是获得主线程信息的一个例子:

public class CurrentThreadDemo {

    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getId() + " " + thread.getName());
    }
}

线程底层实现

在 JDK 1.2 之后,Java 线程模型基于操作系统的原生线程模型来实现。在 Sun JDK 中,它的 Windows 和 Linux 版本都是使用一对一的线程模型来实现的,一条 Java 线程被映射到一条轻量级进程之中,而在 Solaris 平台中,由于操作系统支持一对一或者多对多的线程模型,所以 Solaris 的 JDK 版本提供了相关参数来指定虚拟机使用哪种线程模型。

参考文章

results matching ""

    No results matching ""