创建线程的方法

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

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

@FunctionalInterface
public 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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值