创建线程的方法

来源:互联网 发布:输入网络凭据是什么 编辑:程序博客网 时间:2024/06/11 16:19

从操作系统的角度来看,线程是CPU调度的最小单位——可以理解为一种资源、权利,即能够获得CPU的使用权

网上很多文章中说java创建线程的方法有两种(Thread Runnable)或三种(外加Callable Future),这种说法不能说是错误的,但是很容易误导人,实际上,创建线程(拥有CPU使用权的线程)只有一种方法,那就是继承Thread类。实现Runnable(包括Callable Future)准确地说,不是线程,而是任务,站在操作系统的角度来看,一个类实现类Runnable,并没有立刻获得能够使用CPU的权利,来看一下Runnable和Thread的源码

@FunctionalInterfacepublic interface Runnable {    /**     * When an object implementing interface <code>Runnable</code> is used     * to create a thread, starting the thread causes the object's     * <code>run</code> method to be called in that separately executing     * thread.     * <p>     * The general contract of the method <code>run</code> is that it may     *     * @see     java.lang.Thread#run()     */    public abstract void run();}
public class Thread implements Runnable {    private ThreadGroup group;    public synchronized void start() {        if (threadStatus != 0)            throw new IllegalThreadStateException();        group.add(this);        boolean started = false;        try {            start0();            started = true;        } finally {            try {                if (!started) {                    group.threadStartFailed(this);                }            } catch (Throwable ignore) {            }        }    }    private native void start0();    /**     * If this thread was constructed using a separate     * <code>Runnable</code> run object, then that     * <code>Runnable</code> object's <code>run</code> method is called;     * otherwise, this method does nothing and returns.     * @see     #start()     */    @Override    public void run() {        if (target != null) {            target.run();        }    }

注释我去掉了一些,从Thread.run()方法来看,Runnable要是能运行的话——获得CPU的使用权,那么要放在Thread的构造函数中,否则不执行,然后看Thread.start()方法,其中调用了start0()方法,该方法是个本地方法,本地方法的实现要去看源码,openjdk8的源码下载地址,因为最终是要看cpp的,而我不会cpp,引用另外两篇博文:调用Thread.start()方法,JVM(Hotspot)会调用操作系统的API来创建一个操作系统线程,此时一个Java线程对象就与一个操作系统线程一一对应。

那么为什么需要Runnable接口呢?只有Thread不就够类么,重写run()方法就行了,网上很多都说Runnable是接口,而Thread是类,实现Runnable能够避免Java单根继承带来的局限,且实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源的共享

我认为都没说到点子上,重点是:调用Thread.start()时,是通过操作系统来创建线程的,而创建线程必然是运行在内核态,那么就一定有上下文切换,从而带来额外的时间和空间开销。如果将线程(Thread对象)看作插槽,它代表了资源——能够使用CPU(其实还有寄存器等存储资源),Runnable对象看作插头——要运行的任务,因为创建、撤销插槽开销很大,那么可以先创建一组插槽,什么任务需要运行,就插入相应的插头,就能够避免频繁的创建、撤销,避免上下文切换,从而提高性能,没错,就是线程池的思想

很多地方的线程和任务并没有分清楚,比如这里,若是站在java程序员的角度,Runnable就是一个线程,为啥?我要在执行多个任务,实现Runnable接口,不就是线程么

1.1实现Runnable接口

public class MyRunnable{    public static void main(String[] args) {        Thread[] ths = new Thread[3];        for (int i = 0; i < 3; i++) {            ths[i] = new Thread(new Runnable() {                @Override                public void run() {                    for (int j = 0; j < 5; j++)                        System.out.print(j);                    System.out.print(" ");                }            });        }        for (Thread th: ths)            th.start();    }}

注意Runnable使用了匿名内部类,

1.2继承自Thread类

public class MyThread extends Thread{    @Override    public void run(){        for (int i = 0; i < 5; i++)            System.out.print(i);        System.out.print(" ");    }    public static void main(String[] args){        MyThread[] thx = {                new MyThread(),                new MyThread(),                new MyThread(),        };        for (MyThread th: thx){            th.start();        }    }}

需要注意的是,以上两个程序中,线程的启动顺序是确定的,但是执行顺序是不确定的,因为是并发执行而不是顺序执行的,JVM和操作系统一起决定了线程的执行顺序,下图分别为Runnable在windown7+jdk1.7.0_67和ubuntu16.04+jdk1.8.0_101上运行的结果

**MyRunnableWindows**
**MyRunnableUbuntu**

参考资料:
1. java编程思想
2. http://www.ticmy.com/?p=230
3. http://blog.csdn.net/jeffhtlee/article/details/12751825

0 0
原创粉丝点击