说到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(); }}
运行结果如下(不同时刻运行结果不一样):
这里没考虑安全问题。 可以看到,这里三个线程调度器共同执行一个任务,才能实现线程变量共享:线程调度器A操作改变count值,然后线程调度器B接着操作改变count……
以上就是对Java线程编程概念的初步理解……