Java 并发编程——多线程基础

你真的了解线程吗?创建线程的常用方式有哪些?为什么不能重复调用 Start 方法?什么是单继承的局限?生产者与消费者如何实现?

1. 进程与线程

1.1 进程

进程是系统资源(CPU、内存等)分配的最小单位,它是程序执行时的一个实例。

通常情况下,进程和程序、应用可以看作是同一个概念,但一个程序可能有多个进程,例如,你可以在一台电脑上同时打开多个 QQ。打开的每一个 QQ 都对应一个进程,但所有的这些进程都属于程序 QQ。如下图:

image

每一个程序运行时系统都会为其创建一个进程,并为其分配资源,把该进程放入进程就绪队列,当进程调度器选中它时,就会为它分配 CPU 时间,此时程序才开始真正运行。因此,从微观上来看,程序的每一次运行,都可以看作是 “代码加载→代码执行→执行完毕” 一个完整的过程 。

1.2 线程

线程是 CPU 调度的最小单位,是“轻量级”的进程。

1.3 进程与线程的关系

  • 进程是线程的载体,一个进程至少有一个线程
  • 系统分配给每一个进程的资源在进程间是不共享,但每一个进程中的所有线程共享系统分配给该进程的资源
  • 进程的创建资源消耗较大,线程的创建几乎不需要任何资源,因为它所需要的资源,在创建进程的时候已经加载进来了
  • 相比于进程间通信,线程间通信十分简单

举个生活中的例子,进程相当于双向四车道的马路,线程相当于这条马路上的一条车道。

2. 常用的创建线程的方法

通常情况下,创建线程的方法有两种:

  • 继承 Thread 类
  • 实现 Runnable 接口

2.1 继承 Thread 类

2.1.1 语法
class 类名称 extends Thread{
    属性...;
    方法...;
    public void run(){
        线程主体;
    }
}
2.1.2 实例
//源码:
public class ThreadByExtends_201810252239 extends Thread {
    
    public ThreadByExtends_201810252239() {}
    
    public ThreadByExtends_201810252239(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }
    
    public static void main(String[] args) {
        ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王盖地虎");
        t1.start();
    }
    
}

//执行结果:
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4

2.2 实现 Runnable 接口

2.2.1 语法
class 类名称 implements Runnable{
    属性...;
    方法...;
    public void run(){
        线程主体;
    }
}
2.2.2 实例
//源码:
public class ThreadByImplements_201810260617 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

    public static void main(String[] args) {
        ThreadByImplements_201810260617 r1 = new ThreadByImplements_201810260617();
        Thread t1 = new Thread(r1, "THREAD-A-小鸡炖蘑菇");
        t1.start();
    }

}

//执行结果:
THREAD-A-小鸡炖蘑菇  0
THREAD-A-小鸡炖蘑菇  1
THREAD-A-小鸡炖蘑菇  2
THREAD-A-小鸡炖蘑菇  3
THREAD-A-小鸡炖蘑菇  4

2.3 相关问题

2.3.1 通过继承 Thread 类创建线程时,为什么一定要覆写 run 方法?

查看 run 方法在 Thread 类中的定义:

//源码:
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

由 run 方法定义可知,默认情况下,run 方法的执行最终调用的是 runnable 接口中的方法,所以如果通过继承 Thread 实现线程的时候不覆写 run 方法,那这个线程最终什么事也没做。

2.3.2 启动线程的时候,为什么调用的是 start 方法而不是 run 方法?能直接调用 run 方法吗?

查看 start 方法在 Thread 类中的定义:

//源码:
public synchronized void start() {
    ...

    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();

由 start 方法定义可知,start 方法最终调用的是 start0 方法,在 start0 方法声明处使用了 native 关键字,该关键字表示调用本机操作系统函数,因此此处线程的启动必须通过 start 方法。

如果直接调用 run 方法会出现什么情况?

//源码:
public class ThreadByExtends_201810261951 extends Thread{

    public ThreadByExtends_201810261951() {}
    
    public ThreadByExtends_201810261951(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

    public static void main(String[] args) {
        ThreadByExtends_201810261951 t1 = new ThreadByExtends_201810261951("THREAD-A-宝塔镇河妖");
        t1.start();
        ThreadByExtends_201810261951 t2 = new ThreadByExtends_201810261951("THREAD-B-铁锅炖大鹅");
        t2.run();
    }

}

//执行结果:
main  0
main  1
main  2
THREAD-A-宝塔镇河妖  0
THREAD-A-宝塔镇河妖  1
main  3
main  4
THREAD-A-宝塔镇河妖  2
THREAD-A-宝塔镇河妖  3
THREAD-A-宝塔镇河妖  4

由执行结果可知,当直接调用 run 方法的时候,实际上和普通方法的调用并没有任何区别——方法的运行是在 run 方法的调用线程,而不是子线程。

2.3.3 为什么不能重复调用 start 方法?

如果直接调用两次 start 方法会出现什么情况呢?

//源码:
public ThreadByExtends_201810262030() {}
    
    public ThreadByExtends_201810262030(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

    public static void main(String[] args) {
        ThreadByExtends_201810262030 t1 = new ThreadByExtends_201810262030("THREAD-A-宝塔镇河妖");
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.start();
    }

}

//执行结果:
THREAD-A-宝塔镇河妖  0
THREAD-A-宝塔镇河妖  1
THREAD-A-宝塔镇河妖  2
THREAD-A-宝塔镇河妖  3
THREAD-A-宝塔镇河妖  4
Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Thread.java:705)
    at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810262030.main(ThreadByExtends_201810262030.java:42)

由执行结果可知,当直接调用两次 start 方法时,程序会抛出 IllegalThreadStateException 异常。

接下来,看看 start 方法在 Thread 类中是如何定义的:

//源码:
public synchronized void start() {
    ...
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
   ...
}

private native void start0();

由 start 方法定义可知,当 threadStatus != 0 时,程序会抛出 IllegalThreadStateException()。那 threadStatus 对应的是什么呢?查看 threadStatus 在 Thread 中的定义:

//源码:
/* Java thread status for tools,
 * initialized to indicate thread 'not yet started'
 */

private volatile int threadStatus = 0;

由 threadStatus 变量的注释可知,当线程还没有启动的时候,threadStatus = 0。另外,由 start 方法定义中的注释可知,当 threadStatus = 0 时,线程对应的状态是 NEW。那这个 NEW 是在哪里定义的呢?查看 Thread 类定义可知,NEW 是枚举类 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;
}

由枚举类 State 定义可知,当 Thread 启动之后,便会进入其他状态。当线程执行完毕的时候,线程的状态会变成 TERMINATED 而不是 NEW,因此,重复调用 start 方法时,程序会抛异常。

为了验证上面的分析,将之前的代码略作修改:

//源码:
public class ThreadByExtends_201810262030 extends Thread{

    public ThreadByExtends_201810262030() {}
    
    public ThreadByExtends_201810262030(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

    public static void main(String[] args) {
        ThreadByExtends_201810262030 t1 = new ThreadByExtends_201810262030("THREAD-A-宝塔镇河妖");
        System.out.println(t1.getState());
        t1.start();
        System.out.println(t1.getState());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(t1.getState());
        t1.start();
    }

}

//执行结果:
NEW
RUNNABLE
THREAD-A-宝塔镇河妖  0
THREAD-A-宝塔镇河妖  1
THREAD-A-宝塔镇河妖  2
THREAD-A-宝塔镇河妖  3
THREAD-A-宝塔镇河妖  4
TERMINATED
Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Thread.java:705)
    at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810262030.main(ThreadByExtends_201810262030.java:45)

由执行结果可知,当线程还没有调用 start 方法的时候,此时 State 对应的是 NEW;当线程调用 start 方法之后,此时 State 对应的是 RUNNABLE;当线程执行完毕之后,此时 State 对应的是 TERMINATED,即:当再次调用 start 方法时,State 对应的不是 NEW,故抛异常。

2.3.4 两种创建线程方式的联系及区别
2.3.4.1 联系

观察 Thread 类部分代码:

//源码:
public class Thread implements Runnable {
…
/* What will be run. */
private Runnable target;
…

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
    …
    this.target = target;
    …
}

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

由 Thread 类定义可知,Thread 与 Runnable 实现类是代理的关系,Thread 是 Runnable 实现类的代理,这也解释了为什么两种实现方式最终都需要通过 Thread 启动线程。

image
2.3.4.2 区别
  1. 继承 Thread 有单继承局限;
  2. 资源共享方式不同。

两种创建线程的方式在资源共享方面区别还是挺大的,不过由于资源共享涉及到同步问题。因此,将此部分概念放到 《死锁与同步》模块详述。

2.3.5 多个 Thread 使用同一个 Runnable 对象,最终创建的线程是同一个吗?它们之间什么关系?多个 Thread 使用多个 Runnable 呢?
2.3.5.1 多个 Thread 单个 Runnable

多个 Thread 使用同一个 Runnable 对象时,最终会创建多个 Thread 对象。由于多个 Thread 使用的是同一个 Runnable,所以,最终多个 Thread 之间数据共享,如:

//源码:
public class ThreadByImplements_201810262032 implements Runnable {

    private int bandit = 10;
    
    @Override
    public void run() {
        for (int i = 0; i < 25; i++) {
            if (bandit > 0) {
                System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
            }
        }
    }

    public static void main(String[] args) {
        ThreadByImplements_201810262032 r1 = new ThreadByImplements_201810262032();
        new Thread(r1,"THREAD-张飞").start();
        new Thread(r1,"THREAD-赵云").start();
        new Thread(r1,"THREAD-吕布").start();
        new Thread(r1,"THREAD-董卓").start();
        new Thread(r1,"THREAD-孙尚香").start();
        new Thread(r1,"THREAD-华佗").start();
        new Thread(r1,"THREAD-曹操").start();
        new Thread(r1,"THREAD-夏侯惇").start();
        new Thread(r1).start();
        new Thread(r1).start();
    }

}

//执行结果:
THREAD-张飞  杀了  10  土匪  1578680387
THREAD-孙尚香  杀了  6  土匪  1578680387
THREAD-华佗  杀了  4  土匪  1578680387
THREAD-董卓  杀了  7  土匪  1578680387
THREAD-吕布  杀了  8  土匪  1578680387
THREAD-赵云  杀了  9  土匪  1578680387
THREAD-董卓  杀了  1  土匪  1578680387
THREAD-华佗  杀了  2  土匪  1578680387
THREAD-孙尚香  杀了  3  土匪  1578680387
THREAD-张飞  杀了  5  土匪  1578680387

上面的执行结果不仅验证了推论——多个 Thread 使用同一个 Runnable 时,多个 Thread 之间是数据共享的,同时还说明 Thread 对象的个数是由创建 Thread 对象的次数决定的,跟 Runnable 个数没有关系。

2.3.5.2 多个 Thread 多个 Runnable

多个 Thread 使用多个 Runnable 对象时,最终也会创建多个 Thread 对象。同样的,由于多个 Thread 没有使用同一个 Runnable 对象,所以,最终多个 Thread 之间数据不共享,如:

//源码:
public class ThreadByImplements_201810262032 implements Runnable {

    private int bandit = 10;
    
    @Override
    public void run() {
        for (int i = 0; i < 25; i++) {
            if (bandit > 0) {
                System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
            }
        }
    }

    public static void main(String[] args) {
        ThreadByImplements_201810262032 r1 = new ThreadByImplements_201810262032();
        ThreadByImplements_201810262032 r2 = new ThreadByImplements_201810262032();
        ThreadByImplements_201810262032 r3 = new ThreadByImplements_201810262032();
        ThreadByImplements_201810262032 r4 = new ThreadByImplements_201810262032();
        ThreadByImplements_201810262032 r5 = new ThreadByImplements_201810262032();
        ThreadByImplements_201810262032 r6 = new ThreadByImplements_201810262032();
        ThreadByImplements_201810262032 r7 = new ThreadByImplements_201810262032();
        ThreadByImplements_201810262032 r8 = new ThreadByImplements_201810262032();
        ThreadByImplements_201810262032 r9 = new ThreadByImplements_201810262032();
        ThreadByImplements_201810262032 r10 = new ThreadByImplements_201810262032();
        new Thread(r1,"THREAD-张飞").start();
        new Thread(r2,"THREAD-赵云").start();
        new Thread(r3,"THREAD-吕布").start();
        new Thread(r4,"THREAD-董卓").start();
        new Thread(r5,"THREAD-孙尚香").start();
        new Thread(r6,"THREAD-华佗").start();
        new Thread(r7,"THREAD-曹操").start();
        new Thread(r8,"THREAD-夏侯惇").start();
        new Thread(r9).start();
        new Thread(r10).start();
    }

}

//执行结果:
THREAD-赵云  杀了  10  土匪  258726645
THREAD-孙尚香  杀了  10  土匪  1513896901
THREAD-孙尚香  杀了  9  土匪  1513896901
THREAD-董卓  杀了  10  土匪  786514993
THREAD-吕布  杀了  10  土匪  441874245
THREAD-曹操  杀了  10  土匪  679081647
THREAD-曹操  杀了  9  土匪  679081647
THREAD-曹操  杀了  8  土匪  679081647
THREAD-张飞  杀了  10  土匪  1642210515
THREAD-曹操  杀了  7  土匪  679081647
THREAD-夏侯惇  杀了  10  土匪  1583687396
THREAD-吕布  杀了  9  土匪  441874245
THREAD-吕布  杀了  8  土匪  441874245
THREAD-董卓  杀了  9  土匪  786514993
THREAD-华佗  杀了  10  土匪  1785098644
THREAD-孙尚香  杀了  8  土匪  1513896901
…
2.3.6 什么是单继承局限?

一言以蔽之:只能继承一个父类。因为 Thread 是普通类,因此通过继承 Thread 类创建线程的时候,不能再继承其他类。相比于继承 Thread 类创建线程的方法,通过实现 Runnable 创建线程的方法显得更灵活,因为 Runnable 的实现类可以同时实现多个接口。

2.3.7 Thread.currentThread() 和 this.currentThread() 联系和区别

说实话,当我第一次被问到这个问题的时候有点懵逼,因为我也没有仔细想过这个问题。就像你女朋友有天突然问你“如果我跟你妈同时掉进河里,你先救谁?”一样,只是觉得有些突兀。其实只要稍微看下 currentThread 方法在 Thread 类中的定义就可以一目了然了。

//源码:
/**
 * Returns a reference to the currently executing thread object.
 *
 * @return  the currently executing thread.
 */
public static native Thread currentThread();

这个方法就是一个静态的调用本地方法的方法,没了。很多人不懂的地方可能不在于方法的定义上,而在于它们方法的使用上。

在使用上二者的区别在于,前者能用在所有的类(Thread 类、非 Thread 类)中,后者只能用在 Thread 中,与此同时,两者在 Thread 类中的使用效果并没有什么区别。

如果你还是不懂,那这样问可能就懂了:currentThread()、this.currentThread() 和 Thread.currentThread() 有什么区别?

//源码:
public class ThreadByExtend_201809052254 extends Thread {
    
    public ThreadByExtend_201809052254() {}
    
    public ThreadByExtend_201809052254(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        System.out.println(currentThread().getName());
        System.out.println(this.currentThread().getName());
        System.out.println(Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        ThreadByExtend_201809052254 t1 = new ThreadByExtend_201809052254("THREAD-A-天王盖地虎");
        t1.start();
    }
    
}

//执行结果:  
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎


//源码:
public class ThreadByImplement_201809060656 implements Runnable {

    @Override
    public void run() {
        //此处如果使用此函数系统会提示 The method currentThread() is undefined for the type ThreadByImplement_201809052225
//      System.out.println(currentThread().getName());
        //此处如果使用此函数系统会提示 The method currentThread() is undefined for the type ThreadByImplement_201809052225
//      System.out.println(this.currentThread().getName());
        //此函数无影响
        System.out.println(Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        ThreadByImplement_201809060656 r1 = new ThreadByImplement_201809060656();
        Thread t1 = new Thread(r1,"THREAD-A-天王盖地虎");
        Thread t2 = new Thread(r1,"THREAD-B-小鸡炖蘑菇");
        Thread t3 = new Thread(r1,"THREAD-C-宝塔镇河妖");
        t1.start();
        t2.start();
        t3.start();
    }

}

//执行结果:
THREAD-A-天王盖地虎
THREAD-C-宝塔镇河妖
THREAD-B-小鸡炖蘑菇

这种问题产生的根本原因是基本概念不清,直接导致思考问题的出发点偏离正轨,进而导致行动错误,最终导致一些“神奇”的问题。

2.3.8 为什么不能调用 Stop?如何停止线程?

查看 stop 方法在 Thread 类中的定义:

//源码:
/**
 * …
 * @deprecated This method is inherently unsafe.  Stopping a thread with
 *       Thread.stop causes it to unlock all of the monitors that it
 *       has locked (as a natural consequence of the unchecked
 *       <code>ThreadDeath</code> exception propagating up the stack).  If
 *       any of the objects previously protected by these monitors were in
 *       an inconsistent state, the damaged objects become visible to
 *       other threads, potentially resulting in arbitrary behavior.  Many
 *       uses of <code>stop</code> should be replaced by code that simply
 *       modifies some variable to indicate that the target thread should
 *       stop running.  The target thread should check this variable
 *       regularly, and return from its run method in an orderly fashion
 *       if the variable indicates that it is to stop running.  If the
 *       target thread waits for long periods (on a condition variable,
 *       for example), the <code>interrupt</code> method should be used to
 *       interrupt the wait.
 */
@Deprecated
public final void stop() {}

由 stop 方法注释(This method is inherently unsafe)可知,该方法在操作时会产生死锁的问题,因此不建议使用。

在 stop 方法的注释中,除了介绍为什么该方法不建议使用之外,还介绍了如何停止线程——通过变量控制,如:

//源码:
public class ThreadByExtends_201810281058 extends Thread{

    private boolean mStop = false;
    private int mNumber = 0;
    
    public ThreadByExtends_201810281058() {}
    
    public ThreadByExtends_201810281058(String n) {
        super(n);
    }
    
    public boolean isStop() {
        return mStop;
    }

    public void setStop(boolean stop) {
        this.mStop = stop;
    }

    @Override
    public void run() {
        super.run();
        while (!mStop) {
            System.out.println(Thread.currentThread().getName() + "  " + (++mNumber));
        }
        System.out.println(Thread.currentThread().getName() + "  " + "停止了");
    }

    public static void main(String[] args) {
        ThreadByExtends_201810281058 t1 = new ThreadByExtends_201810281058("THREAD-A-天王盖地虎");
        t1.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.setStop(true);
    }

}

//执行结果:
…
THREAD-A-天王盖地虎  26
THREAD-A-天王盖地虎  27
THREAD-A-天王盖地虎  28
THREAD-A-天王盖地虎  29
THREAD-A-天王盖地虎  30
THREAD-A-天王盖地虎  31
THREAD-A-天王盖地虎  停止了

由上面的示例可知,通过控制变量的方法完全可以达到停止线程的目的。

2.3.9 除了这种创建线程的方法,还有其他方法吗?是什么?

创建线程的方法除了上面提到的两种之外,还有其他方法,如: Callable + Future

//源码:
public class ThreadByCallable_201810281535 implements Callable<Integer>{

    private int result;
    
    @Override
    public Integer call() throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
            result = i;
        }
        return result;
    }
    
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        ThreadByCallable_201810281535 c1 = new ThreadByCallable_201810281535();
        Future<Integer> result = executor.submit(c1);
        executor.shutdown();
        try {
            System.out.println("Result " + result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

//执行结果:
pool-1-thread-1  0
pool-1-thread-1  1
pool-1-thread-1  2
pool-1-thread-1  3
pool-1-thread-1  4
Result 4

由于其他创建线程的方法涉及本文未提到的知识,故在此不详述。

3. 线程的状态(生命周期)

任何一个线程都有五种状态,即:创建、就绪、运行、阻塞、终止。线程各状态之间的转换如下图所示:

image

3.1 创建状态

使用 new 关键字创建完线程对象之后,线程就进入该状态。也就是说,系统给 Thread 对象分配了内存之后,这个对象就处于这个状态了。

3.2 就绪状态

当 Thread 对象调用 start 方法之后,线程就进入该状态。处于这个状态的线程将进入线程队列排队,等待 CPU 的调度。

3.3 运行状态

当线程队列中的线程被 CPU 调度的时候,线程就进入该状态。此时将执行线程的 run 方法。

3.4 阻塞状态

当处于运行状态的线程调用 suspend、sleep、wait 等方法时,线程就进入该状态。处于阻塞状态的线程不能进入线程队列排队,只有当引起阻塞的原因消失之后,线程才能进入线程队列排队。

3.5 阻终止状态

当线程调用 stop 方法或者 run 方法执行完毕之后,线程就进入该状态。处于该状态的线程不能再运行,详情请参考《2.3.3 为什么不能重复调用 start 方法?》。

4. 线程常用方法

4.1 线程名

4.1.1 语法
public final String getName()
4.1.2 示例
//源码:
public class ThreadByExtends_201810281917 extends Thread{
    
    public ThreadByExtends_201810281917() {}
    
    public ThreadByExtends_201810281917(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

    public static void main(String[] args) {
        ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王盖地虎");
        ThreadByExtends_201810252239 t2 = new ThreadByExtends_201810252239("THREAD-B-宝塔镇河妖");
        ThreadByExtends_201810252239 t3 = new ThreadByExtends_201810252239("THREAD-C-小鸡炖蘑菇");
        ThreadByExtends_201810252239 t4 = new ThreadByExtends_201810252239();
        ThreadByExtends_201810252239 t5 = new ThreadByExtends_201810252239();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }

}

//执行结果:
THREAD-A-天王盖地虎  0
Thread-0  0
Thread-0  1
THREAD-B-宝塔镇河妖  0
THREAD-B-宝塔镇河妖  1
THREAD-B-宝塔镇河妖  2
THREAD-C-小鸡炖蘑菇  0
THREAD-C-小鸡炖蘑菇  1
THREAD-B-宝塔镇河妖  3
Thread-0  2
Thread-0  3
Thread-0  4
Thread-1  0
Thread-1  1
Thread-1  2
Thread-1  3
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
Thread-1  4
THREAD-B-宝塔镇河妖  4
THREAD-C-小鸡炖蘑菇  2
THREAD-C-小鸡炖蘑菇  3
THREAD-C-小鸡炖蘑菇  4

4.2 状态检查

4.2.1 语法
public final native boolean isAlive()
4.2.2 示例
//源码:
public class ThreadByExtends_201810281928 extends Thread{
    
    public ThreadByExtends_201810281928() {}
    
    public ThreadByExtends_201810281928(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

    public static void main(String[] args) {
        ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王盖地虎");
        System.out.println("Is Alive" + " " + t1.isAlive());
        t1.start();
        System.out.println("Is Alive" + " " + t1.isAlive());
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Is Alive" + " " + t1.isAlive());
    }

}

//执行结果:
Is Alive false
Is Alive true
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
Is Alive false

4.3 强制运行

4.3.1 语法
public final native boolean isAlive()
4.3.2 示例
//源码:
//1. Normal
public class ThreadByExtends_201810281950 extends Thread{
    
    public ThreadByExtends_201810281950() {}
    
    public ThreadByExtends_201810281950(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

    public static void main(String[] args) {
        outputNormal();
//      outputJoin();
    }
    
    private static void outputNormal(){
        ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
        t1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("Main" + " " + i);
        }
    }
    
    private static void outputJoin(){
        ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
        t1.start();
        for (int i = 0; i < 10; i++) {
            if(i == 5){
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Main" + " " + i);   
        }
    }

}

//执行结果:
Main 0
Main 1
Main 2
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
Main 3
Main 4
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
Main 5
THREAD-A-天王盖地虎  5
THREAD-A-天王盖地虎  6
THREAD-A-天王盖地虎  7
Main 6
Main 7
Main 8
THREAD-A-天王盖地虎  8
Main 9
THREAD-A-天王盖地虎  9

//源码:
//2. Join
public class ThreadByExtends_201810281950 extends Thread{
    
    public ThreadByExtends_201810281950() {}
    
    public ThreadByExtends_201810281950(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

    public static void main(String[] args) {
//      outputNormal();
        outputJoin();
    }
    
    private static void outputNormal(){
        ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
        t1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("Main" + " " + i);
        }
    }
    
    private static void outputJoin(){
        ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
        t1.start();
        for (int i = 0; i < 10; i++) {
            if(i == 5){
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Main" + " " + i);   
        }
    }

}

//执行结果:
Main 0
Main 1
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
Main 2
Main 3
Main 4
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
THREAD-A-天王盖地虎  5
THREAD-A-天王盖地虎  6
THREAD-A-天王盖地虎  7
THREAD-A-天王盖地虎  8
THREAD-A-天王盖地虎  9
Main 5
Main 6
Main 7
Main 8
Main 9

由上面的示例可知,当线程对象调用 join 方法之后,其他正在运行的线程必须让出 CPU,等待此线程完成之后才可以继续运行。

4.4 休眠

4.4.1 语法
public static native void sleep(long millis)
4.4.2 示例
//源码:
public class ThreadByExtends_201810282125 extends Thread{

    public ThreadByExtends_201810282125() {}
    
    public ThreadByExtends_201810282125(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

    public static void main(String[] args) {
        ThreadByExtends_201810282125 t1 = new ThreadByExtends_201810282125("THREAD-A-天王盖地虎");
        t1.start();
    }   

}

//执行结果:
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4

从上面的执行结果里是不是感觉不到线程休眠的效果,那就用心去感受吧,少年。

4.5 中断执行

4.5.1 语法
public void interrupt()
4.5.2 示例
//源码:
public class ThreadByExtends_201810282135 extends Thread{

    public ThreadByExtends_201810282135() {}
    
    public ThreadByExtends_201810282135(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        System.out.println(Thread.currentThread().getName() + "  1.开始休眠");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "  *.休眠中断");
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "  2.休眠结束");
    }

    public static void main(String[] args) {
        ThreadByExtends_201810282135 t1 = new ThreadByExtends_201810282135("THREAD-A-天王盖地虎");
        t1.start();
        try {
            Thread.sleep(200);
            t1.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

//执行结果:
THREAD-A-天王盖地虎  1.开始休眠
THREAD-A-天王盖地虎  *.休眠中断
java.lang.InterruptedException: sleep interrupted
THREAD-A-天王盖地虎  2.休眠结束
    at java.lang.Thread.sleep(Native Method)
    at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810282135.run(ThreadByExtends_201810282135.java:31)

4.6 后台运行

4.6.1 语法
public final void setDaemon(boolean on)
4.6.2 示例
//源码:
public class ThreadByExtends_201810282143 extends Thread{

    public ThreadByExtends_201810282143() {}
    
    public ThreadByExtends_201810282143(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        while (true) {
            System.out.println(Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        ThreadByExtends_201810282143 t1 = new ThreadByExtends_201810282143("THREAD-A-天王盖地虎");
        t1.setDaemon(true);
        t1.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(t1.getState());
    }

}

//执行结果:
…
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎
RUNNABLE
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎
…

由上面的示例可知,线程主体里面的执行的是死循环,因此,正常情况下,Eclipse 的 Console 会一直输出线程的名字。但由于在启动线程之前调用了 setDaemon 方法,所以,虽然线程的主体是一个死循环,但 Java 进程依然可以结束。

下图就是上面示例控制台输出的数据,不难看出,此时的 Java 进程已经结束,但从日志输出中可以看出,此时的线程处于 RUNNABLE 状态,即:虽然进程结束了,但后台线程仍然在运行。

image

4.7 优先级

4.7.1 语法
public final void setPriority(int newPriority)
4.7.2 示例
//源码:
public class ThreadByExtends_201810292043 extends Thread {

    public ThreadByExtends_201810292043() {}
    
    public ThreadByExtends_201810292043(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }
    
    public static void main(String[] args) {
        ThreadByExtends_201810292043 t1 = new ThreadByExtends_201810292043("THREAD-A-天王盖地虎");
        ThreadByExtends_201810292043 t2 = new ThreadByExtends_201810292043("THREAD-B-宝塔镇河妖");
        ThreadByExtends_201810292043 t3 = new ThreadByExtends_201810292043("THREAD-C-小鸡炖蘑菇");
        t1.setPriority(MIN_PRIORITY);
        t2.setPriority(NORM_PRIORITY);
        t3.setPriority(MAX_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
    }

}

//执行结果:
THREAD-B-宝塔镇河妖  0
THREAD-B-宝塔镇河妖  1
THREAD-B-宝塔镇河妖  2
THREAD-C-小鸡炖蘑菇  0
THREAD-C-小鸡炖蘑菇  1
THREAD-C-小鸡炖蘑菇  2
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-C-小鸡炖蘑菇  3
THREAD-B-宝塔镇河妖  3
THREAD-B-宝塔镇河妖  4
THREAD-C-小鸡炖蘑菇  4
THREAD-A-天王盖地虎  4

由上面的示例可知,虽然线程的给各线程都设置了优先级,但并不代表优先级高就一定先执行,最终决定哪个线程先执行的还是 CPU。

4.8 礼让

4.8.1 语法
public static native void yield()
4.8.2 示例
//源码:
public class ThreadByExtends_201810292053 extends Thread {

    public ThreadByExtends_201810292053() {}
    
    public ThreadByExtends_201810292053(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
            if(i == 2){
                System.out.println(Thread.currentThread().getName() + "  " + "线程礼让");
                Thread.currentThread().yield();
            }
        }
    }
    
    public static void main(String[] args) {
        ThreadByExtends_201810292053 t1 = new ThreadByExtends_201810292053("THREAD-A-天王盖地虎");
        ThreadByExtends_201810292053 t2 = new ThreadByExtends_201810292053("THREAD-B-宝塔镇河妖");
        ThreadByExtends_201810292053 t3 = new ThreadByExtends_201810292053("THREAD-C-小鸡炖蘑菇");
        t1.start();
        t2.start();
        t3.start();
    }

}

//执行结果:
THREAD-A-天王盖地虎  0
THREAD-B-宝塔镇河妖  0
THREAD-B-宝塔镇河妖  1
THREAD-C-小鸡炖蘑菇  0
THREAD-C-小鸡炖蘑菇  1
THREAD-B-宝塔镇河妖  2
THREAD-A-天王盖地虎  1
THREAD-B-宝塔镇河妖  线程礼让
THREAD-C-小鸡炖蘑菇  2
THREAD-C-小鸡炖蘑菇  线程礼让
THREAD-B-宝塔镇河妖  3
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  线程礼让
THREAD-B-宝塔镇河妖  4
THREAD-C-小鸡炖蘑菇  3
THREAD-A-天王盖地虎  3
THREAD-C-小鸡炖蘑菇  4
THREAD-A-天王盖地虎  4

由上面的示例可知,每当线程主体满足条件(i == 2)时,都会让出 CPU,让其他线程先执行,然后直接进入线程队列等待调度。

4.9 相关问题

4.9.1 线程自动命名是如何实现的?

创建线程对象的时候,如果不在构造方法里面传线程名,那么系统将会自动为其命名,如:

//源码:
public class ThreadByExtends_201810292216 extends Thread {

    public ThreadByExtends_201810292216() {}
    
    public ThreadByExtends_201810292216(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        System.out.println(Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        ThreadByExtends_201810292216 t1 = new ThreadByExtends_201810292216("THREAD-A-天王盖地虎");
        ThreadByExtends_201810292216 t2 = new ThreadByExtends_201810292216();
        ThreadByExtends_201810292216 t3 = new ThreadByExtends_201810292216();
        t1.start();
        t2.start();
        t3.start();
    }

}

//执行结果:
THREAD-A-天王盖地虎
Thread-1
Thread-0

一起看下 Thread 构造方法的定义:

//源码:
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

Thread 构造方法中调用了 nextThreadNum 方法,nextThreadNum 定义如下:

//源码:
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

每次,当构造方法中没有传入线程名的时候,threadInitNumber 都会 +1,因此最终实现了上面例子所示的结果。

4.9.2 子线程可以比主线程晚消失吗?

可以,如:

//源码:
public class ThreadByExtends_201810292234 extends Thread {
    
    public ThreadByExtends_201810292234() {}
    
    public ThreadByExtends_201810292234(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        ThreadByExtends_201810292234 t1 = new ThreadByExtends_201810292234("THREAD-A-天王盖地虎");
        t1.start();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

}

//执行结果:
main  0
THREAD-A-天王盖地虎  0
main  1
main  2
main  3
main  4
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
4.9.3 多个线程同时强制运行(join)的时候,哪个先执行?

主线程中,有多个线程同时强制运行时,此时主线程会让出 CPU,直至强制运行的线程执行完才开始继续执行。强制运行线程的执行顺序并没有“先来后到”之分,都是由 CPU 决定的,CPU 想调哪个就执行哪个,即:此时的线程和普通的线程没有任何区别。

//源码:
public class ThreadByExtends_201810281950 extends Thread{
    
    public ThreadByExtends_201810281950() {}
    
    public ThreadByExtends_201810281950(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

    public static void main(String[] args) {
        outputJoinMulti1();
    }
    private static void outputJoinMulti1(){
        ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
        ThreadByExtends_201810281950 t2 = new ThreadByExtends_201810281950("THREAD-B-宝塔镇河妖");
        ThreadByExtends_201810281950 t3 = new ThreadByExtends_201810281950("THREAD-C-小鸡炖蘑菇");
        t1.start();
        t2.start();
        t3.start();
        for (int i = 0; i < 10; i++) {
            if(i == 5){
                try {
                    t1.join();
                    t2.join();
                    t3.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Main" + " " + i);   
        }
    }

}

//执行结果:
…
THREAD-B-宝塔镇河妖  6
Main 3
THREAD-A-天王盖地虎  2
Main 4
THREAD-B-宝塔镇河妖  7
THREAD-C-小鸡炖蘑菇  3
THREAD-B-宝塔镇河妖  8
THREAD-C-小鸡炖蘑菇  4
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
THREAD-C-小鸡炖蘑菇  5
THREAD-B-宝塔镇河妖  9
THREAD-C-小鸡炖蘑菇  6
THREAD-A-天王盖地虎  5
THREAD-C-小鸡炖蘑菇  7
THREAD-A-天王盖地虎  6
THREAD-C-小鸡炖蘑菇  8
THREAD-A-天王盖地虎  7
THREAD-C-小鸡炖蘑菇  9
THREAD-A-天王盖地虎  8
THREAD-A-天王盖地虎  9
Main 5
Main 6
Main 7
Main 8
Main 9
4.9.4 后台运行(daemon)的作用?Thread.setDaemon 之后就可以执行完了?

由 《4.6 后台运行》 的讲解可知,线程在启动之前,如果调用了 setDaemon 方法,那即使线程主体内是死循环,此时的 Java 进程也可以结束,但此时的线程并没有结束——依然在运行(RUNNABLE)。

4.9.5 主方法的优先级
//源码:
public class ThreadByExtends_201810292043 extends Thread {

    public ThreadByExtends_201810292043() {}
    
    public ThreadByExtends_201810292043(String n) {
        super(n);
    }
    
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }
    
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "  " + Thread.currentThread().getPriority());
    }

}

//执行结果:
main  5
//源码:
/**
 * 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;

由 Thread 源码可知,线程优先级 Priority = 5 对应着 NORM_PRIORITY。

4.9.6 线程礼让,如果是多个线程而非两个,会出现什么情况?

Thread 类的 yield 方法并不会产生阻塞,只是让出这一次的 CPU 时间片。让出 CPU 时间片之后,Thread 对象立刻又到线程队列中排队等待调度了,而最终等待调度的线程哪个先执行,还是由 CPU 决定。因此,在主线程中,如果有多个线程礼让,礼让之后,哪个线程先执行,最终由 CPU 决定。


参考文档

1)《Java 开发实战经典》
2)《Thinking in Java》
3)Android Developer Document
4)Java Tutorials

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,037评论 0 23
  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 11,153评论 4 56
  • 单任务 单任务的特点是排队执行,也就是同步,就像再cmd输入一条命令后,必须等待这条命令执行完才可以执行下一条命令...
    Steven1997阅读 1,126评论 0 6
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 2,783评论 3 53
  • 当我已经鬓发斑斑之际,坐在大树之下眺望田野小憩。 途经此地的一个年轻人问我,你这一生中,有什么遗憾。我笑了,时光封...
    造梦森林阅读 365评论 0 0