说到Java多线程编程,大多数人都会想到继承Thread或实现Runnable编程,new 一个Thread实例,调用start()方法,由OS调用即可。具体过程如下:

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("MyThread");
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        System.out.println("运行结束!");
    }}

继承Thread;

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("MyRunnable");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
        System.out.println("运行结束!");
    }}

实现Runnable;

这里,有一个问题,就是无论继承Thread还是实现Runnable,最后都需要有一个Thread实例,来激活线程。那么,这个实例化的Thread实例是什么呢?仅仅代表一个线程。如果是这样,那么实现了Runnable的对象有代表什么,它与Thread有何不同?让我们来仔细看看Java线程。

Java线程

线程,就是进程中独立运行的子任务。通常理解为轻量级进程,多个线程共享一个进程资源。由于线程是由OS分配与调度,因此容易产生资源竞争问题,所以成为整个多线程开发中的难点。

从对线程概念的理解上,可以看到,真正线程应该有两个概念,(线程)任务和(线程)调度。

线程任务

再来看看线程代码。无论继承Thread还是实现Runnable,都需要重写一个run()方法,这里面的代码,是线程执行时真正要执行的方法,也就是说,这是线程任务。没任务,线程干嘛?自己玩!!!肯定不可以。

从Runnable接口来看,它只有一个声明——run,也就是说,你要为它的实现类指派任务。之所以有Runnable接口,一方面是因为Java是单继承,不可能为了线程继承了Thread还去继承其他类,所以必须要有Runnable。第二,从面向对象设计原则——单一职责来看,一个类只负责某一方面的任务就可以了,避免引入多变量,多变量代表多不确定性,失控的机会会加大。所以需要Runnable的存在,只负责指派线程任务。

Thread也是存在run抽象方法的,它本身就是代表线程(具体哪个暂时不说)。由它实例自己然后自己激活也是可以的,要不然,只有一个激活而没有任务,怎么也说不过去。其实Thread也是实现Runnable,自己的任务还参考别人的……

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        System.out.println("运行结束!");
    }

这个MyThread继承自Thread类,并(暗藏)重写了run()方法。没任务玩什么!!!

线程调度

有了任务,就要有人执行,就需要有调度。是谁?就是开篇问到的那个问题。两种方式的线程都需要有一个Thread实例,这就是线程调度。

对于继承式的线程,我们好理解,自己激活自己的任务,那么对于实现式的线程呢,是怎么激活的?

在Thread中,有一个Thread(Runnable target)构造方法,也就是说在Thread里有一个Runnable变量。对此,Thread里是这样注释的:“ What will be run.”。再看看Thread的调度代码:

@Override
public void run() {
     if (target != null) {
         target.run();
    }}

target就是Runnable实例,也就是先看看有没有单独的任务表(可以把Runnable看做是一个任务表),如果没有,再查看自己的任务。

那么,现在就很明显了,Thread类其实更重的职责在于线程调度(虽然也可以指派任务),Runnable是用来指派线程任务。在多线程编程中,有关竞争、安全、同步这种资源处理的问题,都发生在线程任务身上。看看同步关键字synchronized和Thread一点关系都没有。如果不指定锁,那么默认的锁对象就是任务实例。有关状态、优先、通信等,真正涉及到线程本身的运行,这些才是线程调度处理的问题。

线程与共享

为了进一步说明线程任务与线程调度,这里再给出一个常见的线程变量共享例子:

public class MyThread extends Thread {
    private int count = 5;

    public MyThread(){}

    /** 设置线程名称 */
    public MyThread(String name){
        this.setName(name);
    }

    @Override
    public void run() {
        while(count>0){
            count -- ;
            System.out.println("由 " + this.currentThread().getName() + " 计算,count:= " +count);
        }
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        Thread a = new Thread(thread, "A");
        Thread b = new Thread(thread, "B");
        Thread c = new Thread(thread, "C");
        a.start();
        b.start();
        c.start();
    }}

运行结果如下(不同时刻运行结果不一样):

拨开云雾见天日 —— Java多线程编程概念剖析_java

这里没考虑安全问题。 可以看到,这里三个线程调度器共同执行一个任务,才能实现线程变量共享:线程调度器A操作改变count值,然后线程调度器B接着操作改变count……

以上就是对Java线程编程概念的初步理解……