Java 多线程编程

来源:互联网 发布:希腊语词根知乎 编辑:程序博客网 时间:2024/06/06 05:21

理解进程与线程的区别

为了增加操作系统运行的效率,设计出了进程,但是进程的启动和销毁还是很慢的,所以后来人们开始尝试在进程上做进一步优化,产生了线程的概念,即 :线程实在进程的基础上扩充的。线程的启动和销毁将比进程更快,一个进程上可以划分出很多个线程,而进程消失,线程一定消失。Java是为数不多的支持多线程编程的语言之一。

简单来说:
多进程:同一时间段会有多个程序并发执行,轮流抢占CPU资源。
多线程:同一进程内可并发多线程,共享内存单元,所有可以实现同一程序内不同代码块并发执行、访问同一数据。

具体的进程与线程的关系参考 《计算机操作系统》。

Java多线程实现的两种方式

如果要想实现多线的开发,也需要一个线程的主体类,这个类必须继承Thread类或者实现Runnable接口。(实际的开发来讲一定使用的是Runnable接口)

1、继承Thread类

线程的主体类只需要通过extends继承Thread类,那么这个类就可用于线程的控制,当然继承之后用户还需要覆写Thread类之中的run()方法。

public class Thread extends Object implements Runnable{}

run()方法与start()方法

//调用run()方法:class MyThread extends Thread {    private String name ;    public MyThread(String name) {        this.name = name;    }    @Override    public void run() {             for (int x = 0 ; x < 10 ; x++ ) {            System.out.println( this.name + ": x = " + x ) ;        }    }}public class Demo {    public static void main(String args[]) throws Exception{        MyThread mtA = new MyThread("Thread A") ;        MyThread mtB = new MyThread("Thread B") ;        MyThread mtC = new MyThread("Thread C") ;        mtA.run() ;        mtB.run() ;        mtC.run() ;    }}

运行结果:
run()调用结果

可以看到,三个线程是先后独立完成,并没有多线程并发执行。这里如果想要实现多线程并发执行,应该调用start()方法:

Thread类的start()方法:
public void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
抛出:
IllegalThreadStateException - 如果线程已经启动。

调用start()方法的程序结果(因为线程调度和操作系统有关,所以这里的运行结果也取决于操作一同的调度方案,并且每次执行的结果都不相同):

start()调用结果

为什么要通过start() 调用 run() 呢?

下面来看一下Java源码中的start() 方法:

public synchronized void start() {        /**         * This method is not invoked for the main method thread or "system"         * group threads created/set up by the VM. Any new functionality added         * to this method in the future may have to also be added to the VM.         *         * A zero status value corresponds to state "NEW".         */        if (threadStatus != 0)            throw new IllegalThreadStateException();        /* Notify the group that this thread is about to be started         * so that it can be added to the group's list of threads         * and the group's unstarted count can be decremented. */        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() {};

调用start() 方法的时候会调用start0() 方法,而在star0() 方法的声明处有一个native关键字,次关键字表示通过本地的原始函数进行代码的实现。

线程的操作一定是需要通过CPU资源调度,每个操作系统的资源调度是不一样的,所以此方法实际上会有JVM来实现,那么编写的时候只给出一个抽象方法的名称,而具体的实现将根据不同的操作系统进行覆写,所以才实现了可移植性。

所以可以得出结论,当用户执行start() 方法的时候,将进行操作系统的资源调度,调度之后才会执行,所以任何时候只要是启动多线程,都一定要使用Thread类之中的start() 方法。

本方法会抛出一个“IllegalThreadStateException”异常,用throw抛出正常情况下应该用try catch来捕获处理,但是这里面没有捕获处理,因为通过观察“IllegalThreadStateException”的继承结构

> java.lang.Object   >    java.lang.Throwable>       java.lang.Exception>           java.lang.RuntimeException>               java.lang.IllegalArgumentException>                   java.lang.IllegalThreadStateException

发现继承关系中有RuntimeException类,所以可以不做处理。此异常的触发:当一个线程被重复启动的时候抛出次异常。所以一个线程只能够启动一次。

2、实现Runnable接口(解决单继承局限)

public interface Runnable {        public void run() ; //所有方法只有run(),没有start()    }

通过之前的分析可以得出结论,只要是线程的启动一定要依靠Thread类的start() 方法,而Runnable接口之中是没有start() 方法的。那么接下来继续看一下Thread类中构造方法的定义:
public Thread(Runnable target) ; 发现可以接受一个Runnable对象。所以可以通过Thread类的构造函数调用start()方法。

//通过实现Runnable接口实现多线程class MyThread implements Runnable {    private String name ;    public MyThread(String name) {        this.name = name;    }    @Override    public void run() {             for (int x = 0 ; x < 10 ; x++ ) {            System.out.println( this.name + ": x = " + x ) ;        }    }}public class Demo {    public static void main(String args[]) throws Exception{        MyThread mtA = new MyThread("Thread A") ;        MyThread mtB = new MyThread("Thread B") ;        MyThread mtC = new MyThread("Thread C") ;        new Thread(mtA).start() ;        new Thread(mtB).start() ;        new Thread(mtC).start() ;    }}

两种实现方法的区别

通过上面两种多线程方法的实现,我们已经知道了二者之间实现结构是一样的,继承限制不一样,但是还有其他的区别吗?

观察Thread类的定义

public class Thread extends Object implements Runnable

这里写图片描述

从结构上讲Thread是一个代理类的结构设计,但又不是那么完整,如果是一个纯粹的代理结构应该调用的是Runnable接口中的run() 方法,但此处调用的是Thread类的start() 方法。所以在整个的操作之中,虽然形式是代理结构,但还是有些差异的,而这个差异也是由于历史原因造成的。

数据共享

除了上述的基本联系之外,二者还有一点小区别:使用Runnable接口实现的多线程要比使用Thread类实现的多线程更容易表示出数据共享的概念。

范例程序:编写一个简单的卖票程序,利用Thread类实现(产生三个线程)

// 方法一:继承Thread类class MyThread extends Thread {    private int ticket = 5 ;    @Override    public void run() {             for (int x = 0 ; x < 10 ; x++ ) {            if (this.ticket > 0) {                System.out.println( "卖票: ticket =  " + this.ticket -- ) ;            }        }    }}public class Demo {    public static void main(String args[]) throws Exception{        //启动三个线程,但是这里生成了三个MyThread类对象,每个都有自己ticket属性,所以。。。。        new MyThread().start() ;        new MyThread().start() ;        new MyThread().start() ;    }}

运行结果:
这里写图片描述

//方法二:实现Runnable接口class MyThread implements Runnable {    private int ticket = 5 ;    @Override    public void run() {             for (int x = 0 ; x < 10 ; x++ ) {            if (this.ticket > 0) {                System.out.println( "卖票: ticket =  " + this.ticket -- ) ;            }        }    }}public class Demo {    public static void main(String args[]) throws Exception{        //启动三个线程,这里只成了一个MyThread类对象,ticket属性是共享的        Runnable mt = new MyThread() ;        new Thread(mt).start() ;        new Thread(mt).start() ;         new Thread(mt).start() ;    }}

运行结果:
这里写图片描述

通过观察上述两个例子可以看出来,方法二更容易实现数据共享。原因在于:方法一继承Thread类,当要实现多线程并发执行时,需要生成多个类对象,每个类对象各自调用start(),此时对象之间相互独立,每个对象都有自己ticket属性,造成上述结果;方法二只定义了一个类对象Runnable mt = new MyThread() ;,这样启动的三个线程时,ticket属性都是该类对象mt的属性,实现了数据的共享。

二者区别总结

  • 多线程的两种实现方式,继承Thread类或实现Runnable接口,其中Thread类是Runnable的子类;
  • 如果继承了Thread类,会受到单继承局限,而且不方便表示数据共享的概念;
  • 如果实现Runnable类,没有单继承局限,而且方便表示数据共享。
  • 不管使用何种方式,最终一定要通过Thread类的start() 方法才可以启动多线程。
  • 日后多线程的主体结构定义在实现Runnable接口的类中,而多线程的实现使用Thread类的start() 方法。

定义匿名内部类来实现多线程编程

//定义匿名内部类来实现多线程编程public class Demo {    public static void main(String args[]) throws Exception{        //这里的结构为new Thread( new Runnable() {} ).start() ; 大括号内定义匿名内部类        new Thread(new Runnable() {            private int ticket = 5 ;            @Override            public void run() {                 for (int x = 0 ; x < 10 ; x++ ) {                if (this.ticket > 0) {                    System.out.println( "卖票: ticket =  " + this.ticket -- ) ;                    }                }            }        }).start() ;    }}
原创粉丝点击