传统线程机制之创建线程的两种方式

什么是线程?怎么叫多线程?

什么是线程:线程就是程序的一条执行线索。程序是一行代码一行代码往下执行,这条执行的路线就是一条线索,就是一个线程。如果,现在在我们的程序内部,同时有多条执行线索,这边有一部分代码在执行,同时另外一边,又有另外一部分代码在同时执行,这两边的代码同时并行执行就出现了两条执行线索。这些线索在我们程序里面称作为线程。在了解多线程以前,是没有办法让你的程序用两条执行线索同时运行的。你一旦进了while循环,这个程序就执行循环体内部的代码,不会执行其它的代码。可不可以有两个while循环同时运行呢?不可以。那如果要有多线程呢?就可以有两个while循环同时在那里循环。这就叫多线程。

创建线程的两种传统方式

Thread

在传统的线程里面,创建线程的方式有两种,一个线程是一个事物(东西),计算机里面有个东西叫线程,它也是一种东西,一个东西在java里面可用一个类去描述它。那么,要搞出那么一个东西,在程序里面就要找出它的相应的一个对象。Java中描述线程的类叫Thread,我们要操作它,首先要得到一个Thread的对象。我们可以用new方法创建一个Thread的对象,那么,就相当于我们在程序里面创建了一个执行线索。那么,创建了这个执行线索之后,我们要让这个线索去运行,那么,我们可以掉用这个对象的start()方法,这个方法的功能就是让这个线索运行。这里一共做了两件事,第一件事是把这个东西搞出来,第二件事是让这个东西开始发挥作用,开始实际运行。线程开始运行的时候呢,就要有一行行代码在它上面走,他只是让CPU知道说,我有一条执行线索了,那么开辟线索,它要运行的时候呢,它上面也要有代码,当这个线索运行的时候,它就要找代码来运行了,他找的代码是什么呢?就是Thread类里面的run方法。我们看到,在这个thread类的方法里面,如果Runnable类型的target引用变量为null的话,他就什么也不做,如果target不为空的话,他就执行target引用的对象的run方法。我们先不讨论怎么给target引用变量赋值的问题,我们先看看如果不给target引用变量赋值,我们怎么怎么让线程跑我们的代码,这里我们就需要把我们的代码写到run方法里面,我们怎么才能把我们要运行的代码写到run方法里面呢,这时,我们就要用到java的多态,我们需要继承这个Thread类,然后重写这个run方法,把我们要运行的代码写到这个run方法里面。这样,当我们为Thread类型的引用变量赋值为我们自己定义的Thread子类的对象的时候,这个时候被调用的run方法就是我们自己重写的这个run方法了。所以,这时候,我们要创建线程呢,就不直接创建Thread的对象了,而是创建这个Thread它的子类的对象,创建它的子类的目的是为了覆盖它内部的一个方法。其实真正起作用的还是这个Thread,只是我没有办法,我要盖掉它的方法,我只能用子类的形式来搞。线程运行起来以后,计算机里面会有很多个线程,它会为线程取一个名字,当线程运行起来以后,我们可以得到当前这个线程对象,进而得到这个对象它表示的线程的名字。Thread.currentThread()这样就得到了当前这个thread对象,然后Thread.currentThread().getName()就得到了这个线程的名字。

    public static void main(String[] args) {

        Thread thread = new Thread() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(500);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    System.out.println("1:" + Thread.currentThread().getName());

                    System.out.println("2:" + this.getName());

                }

            }

        };

        thread.start();

    }

Runnable

另外一种线程的编写方式:Runnable对象不是线程,是线程要运行的代码的宿主,就是说,我要运行的代码是放到这个Runnable对象里面的。所以,Runnable对象不是线程,而是线程所有运行的代码放到这个对象上。

    public static void main(String[] args) {

        Thread thread2 = new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(500);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    System.out.println("1:" + Thread.currentThread().getName());

                }

            }

        });

        thread2.start();

    }

 

这两种创建线程对象的方式有什么区别呢?

创建一个Runnable接口的对象,实现run方法,然后把该Runnable接口的对象作为Thread的构造方法的参数传给Thread,创建一个Thread线程对象的方式,这种方式更体现了面向对象的思想。有一个线程对象,线程要运行代码,所有要运行的代码封装到一个独立的对象里面去,一个叫线程,一个叫线程所运行的代码,这是两件东西。线程所运行的代码应该装在一个对象里面,线程是另外一个独立对象,再两者一组合,这样更加体现面向对象的思想。

Thread类的部分源码:

Public class Thread implements Runnable {

    private Runnable target;

@Override

    public void run() {

        if (target != null) {

            target.run();

        }

}

private void init(ThreadGroup g, Runnable target, String name,

                      long stackSize, AccessControlContext acc) {

        ……

        this.target = target;

        ……

}

private void init(ThreadGroup g, Runnable target, String name,

                      long stackSize) {

        init(g, target, name, stackSize, null);

}

    public synchronized void start() {

        。。。。。

        try {

            start0();

            。。。。。。

        } finally {

            。。。。。。

        }

    }

private native void start0();

}

下面是一道思考题:

思考,以下代码中运行的是Runnable中的run方法,还是Thread中的run方法。

答案是,运行的是Thread里面run方法。因为Thread对象调用的是覆盖后的run方法,而覆盖后的run方法并没有去调用Runnable的run方法的逻辑。所以Runnable里面的run方法不会执行。

    public static void main(String[] args) {

        new Thread(new Runnable() {

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(500);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    System.out.println("runnable :" + Thread.currentThread().getName());

                }

            }

        }) {

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(500);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    System.out.println("thread :" + Thread.currentThread().getName());

                }

            }

        }.start();

    }

在Thread子类覆盖的run方法中编写运行代码

涉及一个以往知识点:

能否在run方法声明上抛出InterruptedException异常,以便省略run方法内部对Thread.sleep()语句的try…catch处理?

不可以,在java 中,当我们子类要重写父类中的方法,如果父类的方法有异常声明,那么子类重写这个方法时候,所要声明的异常不应该比父类的大。只能是小等,或者可以没有。非检查异常:重写方法可以抛出任何非检查的异常,无论被重写方法是否声明了该异常。检查异常:重写方法一定不能抛出新的检查异常,或比被重写方法声明的检查异常更广的检查异常。因为根据多态的思想,我们必须保证所有用到父类方法的地方,子类覆盖了这个方法一样可以无缝替换。如果子类重写的方法抛出了父类方法没有抛出的检查异常,或是抛出了父类方法抛出的检查异常的父类,那么当用到父类方法的地方被替换为子类方法时,就会报错,比如原有的代码没有捕抓到子类方法的异常,或者没有抛出子类方法的异常等。这样就需要根据子类重写的方法抛出的异常不同来不断的重写原有的调用到父类该方法的地方的代码。这样,就失去了多态的意义。也不符合父类是子类共性的抽取。

在传递给Thread对象的Runnable对象的run方法中编写代码

总结:查看Thread类的run()方法的源代码,可以看到其实这两种方式都是在调用Thread对象的run方法,如果Thread类的run方法没有被覆盖,并且为该Thread对象设置了一个Runnable对象,该run方法会调用Runnable对象的run方法。

问题:如果在Thread子类覆盖的run方法中编写了运行代码,也为Thread子类对象传递了一个Runnable对象,那么,线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码?//子类的

答案是,运行的是Thread里面run方法。因为Thread对象调用的是覆盖后的run方法,而覆盖后的run方法并没有去调用Runnable的run方法的逻辑。所以Runnable里面的run方法不会执行。

多线程机制会提高程序的运行效率吗?为什么会有多线程下载呢?

再只有一个CPU的情况下,不会,一般来说还可能会更慢。CPU只有一个,比如我人只有一个,在桌子上做馒头,我就原地不动,就在这做馒头。还有一种情况,一共摆了3张桌子,我在这张桌子上做一个馒头,然后又跑到那张桌子上做一个馒头,然后又跑到另外一张桌子上做一个。那么说,是在一个桌子上做馒头做的快还是在三张桌子上做馒头做的快?肯定是在一张桌子上做馒头快,不用跑来跑去。也就说,你的CPU一会儿跑到这个线程上运行,一会儿又跑到那个线程上运行,两个线程之间的切换会花费时间。所以呢,性能更低。

那么为什么多线程下载会速度更快呢?

其实你的计算机并没有快,而是去抢了服务器的带宽。假如服务器可以同时为100个人提供服务,为每个人提供20k,你一个人去下的话,假如一个线程代表一个人,它给你20k,如果你启动10个线程呢,他就认为有10个人来下,他就给你提供了200k。你计算机本省没快,而是服务器那边给你提供了更多的服务,这样来加速。是去抢了别人的资源,不是让自己更快。

猜你喜欢

转载自my.oschina.net/u/3512041/blog/1822001