Java基础-多线程-①线程的创建和启动

简单阐释进程和线程

对于进程最直观的感受应该就是“windows任务管理器”中的进程管理:

  (计算机原理课上的记忆已经快要模糊了,简单理解一下):一个进程就是一个“执行中的程序”,是程序在计算机上的一次运行活动。程序要运行,系统就在内存中为该程序分配一块独立的内存空间,载入程序代码和资源进行执行。程序运行期间该内存空间不能被其他进程直接访问。系统以进程为基本单位进行系统资源的调度和分配。何为线程?线程是进程内一次具体的执行任务。程序的执行具体是通过线程来完成的,所以一个进程中至少有一个线程。回忆一下 HelloWrold 程序中main方法的执行,其实这时候,Java虚拟机会开启一个名为“main”的线程来执行程序代码。一个进程可以包含多个线程,这些线程共享数据空间和资源,但又分别拥有各自的执行堆栈和程序计数器。线程是CPU调度的基本单位。

多线程

  一个进程包含了多个线程,自然就叫做多线程。拥有多个线程就可以让程序看起来可以“同时”处理多个任务,为什么是看起来呢?因为CPU也分身乏术,只能让你这个线程执行一会儿,好了你歇着,再让另一个线程执行一会儿,下次轮到你的时候你再继续执行。这里的“一会儿”实际上时间非常短,感觉上就是多个任务“同时”在执行。CPU就这样不停的切来切去…既然CPU一次也只能执行一个线程,为什么要使用多线程呢?当然是为了充分利用CPU资源。一个线程执行过程中不可能每时每刻都在占用CPU,CPU歇着的时候我们就可以让它切过来执行其他的线程。

  比如QQ聊天的时候,跟一个人正聊着呢,另一个消息过来了。如果是单线程,不好意思,等我跟这一个聊完说拜拜之后再去理你吧。多线程呢,消息窗口全打开,这个窗口说完话了,总得等人家回吧,趁这个空闲时候,处理另一个窗口的消息。这样看起来不就是同时进行了么,每一个窗口的另一边都以为你只在跟他一个人聊天…

Java中的多线程

  Java中启用多线程有两种方式:①继承Thread类;②实现Runnable接口

There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started.The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. 

继承Thread类

  创建一个类,继承java.lang.Thread,并覆写Thread类的run()方法,该类的实例就可以作为一个线程对象被开启。

/**
 * Dog类,继承了Thread类
 * @author lt
 */
class Dog extends Thread {
    /*
     * 覆写run()方法,定义该线程需要执行的代码
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

线程创建好了,怎么让它作为程序的一个独立的线程被执行呢?创建一个该类的实例,并调用start()方法,将开启一个线程,并执行线程类中覆写的run()方法。

public class ThreadDemo {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.start();
    }
}

看不出什么端倪,如果我们直接调用实例的run()方法,执行效果是完全一样的,见上图。

public class ThreadDemo {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.run();
    }
}

如果一切正常,这时候程序中应该有两个线程:一个主线程main,一个新开启的线程。run()方法中的代码究竟是哪个线程执行的呢?Java程序中,一个线程开启会被分配一个线程名:Thread-x,x从0开始。我们可以打印当前线程的线程名,来看看究竟是谁在执行代码。

class Dog extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("当前线程:" + Thread.currentThread().getName() + "---" + i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        Dog dog = new Dog();
        dog.start();
    }
}

可以看到,确实开启了一个新的线程:Thread-0,main()方法的线程名就叫main。

同一个实例只能调用一次start()方法开启一次,多次开启,将报java.lang.IllegalThreadStateException异常:

我们再创建一个实例,开启第三个线程:

public class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        Dog dog = new Dog();
        Dog dog2 = new Dog();
        dog.start();
        dog2.start();
    }
}

这时候我们已经能够看到多线程的底层实现原理:CPU切换处理、交替执行的效果了。

run和start

  上面我们直接调用run()方法和调用start()方法的结果一样,现在我们在打印线程名的情况下再来看看:

public class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        Dog dog = new Dog();
        Dog dog2 = new Dog();
        dog.run();
        dog2.run();
    }
}

可以看到,这时候并没有开启新的线程,main线程直接调用执行了run()方法中的代码。所以start()方法会开启新的线程并在新的线程中执行run()方法中的代码,而run()方法不会开启线程。查看start()的源代码,该方法调用了本地方法 private native void start0();即调用的是操作系统的底层函数:

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) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

private native void start0();

实现Runnable接口

  第二种方式,实现Runnable接口,并覆写接口中的run()方法,这是推荐的也是最常用的方式。Runnable接口定义非常简单,就只有一个抽象的run()方法。

//Runnable接口源码
public interface Runnable {
    public abstract void run();
}
class Dog implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("当前线程:" + Thread.currentThread().getName() + "---" + i);
        }
    }
}

这时候的Dog类看起来跟线程什么的毫无关系,也没有了start()方法,怎么样开启一个新的线程呢?直接调用run()方法?想想也不行。这时候我们需要将一个Dog类的实例,作为Thread类的构造函数的参数传入,来创建一个Thread类的实例,并通过该Thread类的实例来调用start()方法从而开启线程。

public class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        thread.start();
    }
}

这时候如果要开启第三个线程,需要创建一个新的Thread类的实例,同时传入刚才的Dog类的实例(当然也可以创建一个新的Dog实例)。这时候我们就可以看到跟继承Thread类的方式的区别:多个线程可以共享同一个Dog类的实例。

public class ThreadDemo {
    public static void main(String[] args) {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        Thread thread2 = new Thread(dog);
        thread.start();
        thread2.start();
    }
}

两种方式的比较

  继承Thread类的方式有它固有的弊端,因为Java中继承的单一性,继承了Thread类就不能继承其他类了;同时也不符合继承的语义,Dog跟Thread没有直接的父子关系,继承Thread只是为了能拥有一些功能特性。而实现Runnable接口,避免了单一继承的局限性,同时更符合面向对象的编程方式,即将线程对象进行单独的封装,而且实现接口的方式降低了线程对象(Dog)和线程任务(run方法中的代码)的耦合性,如上面所述,可以使用同一个Dog类的实例来创建并开启多个线程,非常方便的实现资源的共享。实际上Thread类也是实现了Runnable接口。实际开发中多是使用实现Runnable接口的方式。

posted @ 2014-07-18 22:19  罗韬  阅读(21399)  评论(4编辑  收藏  举报