多线程总结-基础知识

843 阅读5分钟

进程和线程

进程

在谈多线程之前,我们需要了解什么是进程,进程是CPU资源分配的基本单位,是一个程序执行的实例。

线程

线程是CPU调度和执行的基本单位,一个进程可以由多个线程,每个线程有自己的堆栈和局部变量,多线程可以共享进程所有资源。对于单核CPU而言,多线程执行其实就是多个线程在同一个CPU间来回切换,交替运行。对于多核CPU,多线程才有可能同时执行。

线程的创建

实现Runnable接口

定义一个类,实现Runnable接口,重写run()方法,在使用时采用Thread类来包装

实现Callable接口

实现Callable接口,重写其call()方法,然后包装成FutureTask

继承自Thread类

定义一个类,继承自Thread类,然后重写其run()方法

一个普通java程序的线程

我们来看一下一个普通java程序的线程数量,以下代码在启动时,可以理解是启动了一个进程,打印出的进程数量也就是一个java进程启动时使用到线程数量, 以下代码是用android studio运行的

public class MultiThread {
    public static void main(String []args) {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        for (ThreadInfo info: threadInfos) {
            System.out.println("[" + info.getThreadId() + "] " + info.getThreadName());
        }
    }
}
输出结果为
[5] Monitor Ctrl-Break
[4] Signal Dispatcher      // 分发处理发送给JVM的线程
[3] Finalizer              // 调用对象finalize方法的线程
[2] Reference Handler      // 清除Reference的线程
[1] main                   // main线程,应用程序入口

main 线程

这个很好理解,就是当前执行的线程

Reference Handler

VM在创建main线程后就会创建,其优先级最高,它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题

Finalizer

主要用于在垃圾收集前,调用对象的finalize()方法。

Signal Dispatcher

Monitor Ctrl-Break

采用idea类的开发工具运行java程序会出现的

更多java线程类型可参看 ifeve.com/jvm-thread/

线程的状态

很多文章中将线程的状态划分为5种,其实是从操作系统的层面来考虑,不仅仅是线程,进程也可以划分为这5种状态。我们先来看这5种状态的划分

从操作系统角度对线程状态划分

New

调用了New方法创建一个线程

Runnable状态

运行状态,java线程将操作系统中的就绪状态和运行中的状态统称为Runnable状态,就绪状态是调用线程的start方法后,线程还在等待CPU分配资源,尚未运行。

运行状态

线程获得CPU资源,处于运行状态

阻塞

调用wait方法后线程就处于阻塞状态,需要依靠其它线程的notify/notifyAll方法来唤醒,唤醒后的线程不会立即执行run方法,需要再次等待CPU分配资源进入运行状态

销毁

线程执行完毕或被强行终止,或出现异常,会被销毁,释放资源

JVM中的线程状态划分

Thread类中就有对线程状态的定义。代码如下:

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;
}

这里将线程分为6种状态

NEW

创建一个线程

Runnable

java线程中将操作系统层的就绪和运行两种状态统称为Runnable 状态

阻塞状态

线程阻塞于锁

等待状态

当线程执行wait()方法后进入此状态,处于此状态的线程需要等待其它线程做出特定动作才能改变当前状态。

超时等待状态

该状态不同于等待状态,它在超时后可以自行返回到状态状态

终止状态

表示线程执行完毕

下图摘自《JAVA并发编程的艺术》, 描述了线程的几种状态之间的切换。 image.png

线程状态变化相关的方法

start

当调用start方法后,线程开始进入可运行状态

wait

对线程调用wait方法时,线程会交出自己持有的锁,进入等待队列,也就是阻塞状态

notify/notifyAll

唤醒在此对象监视器上等待的单个线程或所有线程

yield

调用此方法后,线程放弃获取的cpu时间片,由正在运行的状态转为可运行状态,该方法不会交出锁

sleep

当前线程进入阻塞状态,但不会释放持有的锁。和yeild方法相比,执行了sleep方法的线程在指定的时间内不会执行,而执行了yeild方法的线程可能在进行可执行状态后,又开始执行。

join

在t1线程中对t2线程调用join方法, 会等待t2线程执行完毕,当前线程会阻塞

线程间的通信

共享变量

多个线程访问共享变量来实现线程间的通信,需借助于volatile, synchronized 机制来保证数据的正确性

等待通知机制

等待通知机制,也就是利用wait/notify, notifyAll来实现线程间的通信

管道的输入/输出

public class Piped {
    public static void main(String args[]) throws IOException {
        PipedWriter out = new PipedWriter();
        PipedReader in = new PipedReader();
        out.connect(in);
        Thread printThread = new Thread(new Print(in), "PrintThread");
        printThread.start();
        int receive = 0;
        try {
            while((receive = System.in.read()) != -1) {
                out.write(receive);
            }
        } finally {
            out.close();
        }

    }
    static class Print implements Runnable {
        private PipedReader in;
        public Print(PipedReader in) {
            this.in = in;
        }
        public void run() {
            int received = 0;
            try {
                while((received = in.read()) != -1) {
                    System.out.print((char)received);
                }
            } catch (IOException e) {

            } 
        }
    }
}

以上代码摘自《Java并发编程的艺术》

Thread.join()

在t1线程中对t2线程调用join方法, 会等待t2线程执行完毕,当前线程会阻塞