1.1 创建并运行Java线程

2016-05-31 00:12:16 9,253 3



行是知之始,知是行之成。

个人认为,学习一个东西应该是先知道其运行起来的效果是什么样的,然后再去深挖其中的原理和需要注意的问题。因此本节立刻让读者体验一下多线程运行起来的效果。

作为一个面向对象的语言,Java中线程也是用一个对象(java.lang.Thread)来表示的。每个进程至少有一个线程,作为程序的入口,通常情况下这个线程我们称之为主线程。在Java中,程序的入口是main方法,因此main方法实际上就是运行在主线程中的。

主线程

我们可以通过以下的代码来进行确认。

public class MainThreadDemo {
    public static void main(String[] args) {
        //用于打印主线程的名称
        System.out.println(Thread.currentThread().getName());
    }
}

这段程序的运行结果如下,打印出main,这是主线程的名称,事实上每个线程都有自己的名称。

main

创建自己的线程

主线程作为程序的入口,因此我们如果需要创建自己的线程,那么必须在主线程中进行创建,也就是在main方法中进行创建。在教程的开头,我们说过一个进程中的多个线程是可以同时运行的。以下的案例在主线程中创建了一个自定义的线程,这样我们的程序中就同时有了两个线程在运行,我们希望通过这段代码来确定多个线程是不是真的可以同时运行:

/**
 * 演示多个线程可以并发执行的案例
 */
public class ThreadDemo {
    public static void main(String[] args) {
        //创建一个线程对象,覆盖其run方法,传入参数为线程的名字
        Thread t1=new Thread(){
            @Override
            public void run() {
                for (int i = 1; i <=100 ; i++) {
                 System.out.println("自定义线程循环:"+i+"次");
                }
            }
        };
        //调用start方法启动线程
        t1.start();
        for (int i = 1; i <=100 ; i++) {
            System.out.println("主线程循环:"+i+"次");
        }
    }
}

为了使代码尽量简单,案例中并没有涉及过多的新的API。读者主要关注的就是我们创建了一个线程Thread对象,并覆盖了其run方法,然后调用了其start方法使其运行。然后我们在main方法和线程的run方法中各自循环了100次,并且打印出一些内容。

以下是笔者在本机上运行以上代码的结果部分内容:

...

自定义线程循环:53次

自定义线程循环:54次

自定义线程循环:55次

自定义线程循环:56次

main方法循环:56次

自定义线程循环:57次

main方法循环:57次

自定义线程循环:58次

main方法循环:58次

自定义线程循环:59次

main方法循环:59次

自定义线程循环:60次

main方法循环:60次

自定义线程循环:61次

main方法循环:61次

自定义线程循环:62次

main方法循环:62次

自定义线程循环:63次

main方法循环:63次

自定义线程循环:64次

main方法循环:64次

自定义线程循环:65次

main方法循环:65次

自定义线程循环:66次

main方法循环:66次

main方法循环:67次

main方法循环:68次

main方法循环:69次

...

可以看到main方法中打印的内容和线程run方法中打印的内容有时是交叉运行的,有时又是一个方法循环了好几次才到另一个方法循环(注意,每次的运行结果可能都是不一样的,在后面会讲解原因)。

交叉实现部分说明

线程run方法和main方法交叉打印出来的内容很好的说明了线程是可以并行运行的。如果不能并行运行,肯定是一个方法执行完,才会让另一个方法执行。

重复打印部分说明:

我们会发现,有的时候run方法或者main方法循环了好几次,才会到另一个方法运行。这是因为线程的运行是抢占式的。因为有多个线程,为了达到并行执行的效果,CPU需要一会运行这个线程,一会又去运行别的线程,而在不同的线程切换的过程中,CPU并不是按顺序来主义调度这些线程,线程的运行是抢占式的,意思是一个线程获得一次运行机会之后,下一次能继续请求CPU进行执行。如果CPU连续几次接受了某个线程的执行请求,那么就有可能出现上述情况。

每次运行结果不同说明:

同样还是因为线程的运行是抢占式的原因,我们并不知道程序在每次运行的时候CPU接受到的不同的线程的请求执行的顺序是什么样的。因此对于上述案例,在极端的情况下,可能会出现main方法(或者run方法)先全部打印完,然后再去执行另一个方法的情况。


提示:在调用线程对象的start()方法之前,我们的程序中是只有主线程在运行的。这类似我们运行一个软件,在点击运行图标之前,其只是硬盘上的一个文件而已,只有点击了运行按钮,其才会成为一个进程。对于线程来说是类似的,我们创建了线程对象之后,其仅仅就是Java中的对象而已,只有调用了其start()方法,其才能成为一个真正意义上运行的线程。