现在的位置: 首页 > 综合 > 正文

Java学习札记5:Thread 和 Runnable 的联系和区别(1)

2019年09月07日 ⁄ 综合 ⁄ 共 4438字 ⁄ 字号 评论关闭

在 Java 中有两种方式可以实现多线程,一种是继承 Thread 类,一种是实现 Runnable 接口。


Thread 类

    Thread 类是在 java.lang 包中定义的。一个类只要继承了 Thread 类同时覆写了其中的 run() 方法就可以实现多线程操作了。但是一个类只能继承一个父类,这是此方法的局限,下面看例子:

package org.thread.demo; 

class MyThread extends Thread {
	private String name;

	public MyThread(String name) {
		super();
		this.name = name;
	}

	public void run() {
		for(int i=0; i < 10; i++) {
			System.out.println("线程开始:"+this.name+",i="+i);
		}
	}
}

package org.thread.demo;

public class ThreadDemo01 {
	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程a");
		MyThread mt2 = new MyThread("线程b");
		mt1.run();
		mt2.run();
	}
}

    但是,此时的结果是很有规律的,先执行第一个对象,然后执行第二个对象,并没有体现出多线程并发执行的效果,看下图的结果:


    这是为什么呢?查阅 JDK 文档,我们找到了 start() 方法的原方法声明,一旦执行 start() 方法,就会调用 run() 方法,于是我们把上面的程序稍作修改,通过调用 start() 方法来启动线程:

package org.thread.demo; 

public class ThreadDemo01 {
	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程a");
		MyThread mt2 = new MyThread("线程b");
		mt1.start();
		mt2.start();
	}
}; 

    再来看看这次修改后的执行结果:

    显然,使用 start() 方法可以实现 Java 的多线程机制。那么为什么要使用 start() 方法来启动多线程呢? start() 方法又是怎么实现的?它的内部原理是什么?带着这些问题,我们来一步一步剖析Java 的源代码。

    Thread.java

    /**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */
    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() 方法,其中,使用了 native 关键字来声明 start0() 方法。

    什么是 Native Method?

    简单地说,一个 Native Method 就是一个 Java 调用非 Java 代码的接口。Native Method 是这样一个 Java 的方法:该方法的实现由非 Java 语言实现,一般是 C 。这个特征并非 Java 所特有,很多其它的编程语言都有这一机制,比如在 C++ 中,你可以用extern "c" 告知 C++ 编译器去调用一个 C 的函数。因为 Native
Method 方法,使得 start0() 可以直接调用系统底层的许多函数,这就是所谓的 JNI 技术(Java Native Interface)。



Runnable 接口

    在实际开发中,一般多线程的操作很少使用 Thread 类,而是通过实现 Runnable 接口来完成。

public interface Runnable {
	public void run(); 
}

    例子:

package org.runnable.demo; 

class MyThread implements Runnable {
	private String name;

	public MyThread(String name) {
		this.name = name;
	}

	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("线程开始:" + this.name+",i="+i); 
		}
	} 
}; 

    但是在使用 Runnable 定义的子类时没有发现 start() 方法,只有在 Thread 类中才有。此时观察 Thread 类,有一个构造方法:

public Thread(Runnable target) 

    此构造方法接受 Runnable 的子类实例,也就是说可以通过 Thread 类来启动 Runnable 从而实现多线程操作。(start() 方法可以协调系统的底层资源)

package org.runnable.demo;

import org.runnable.demo.MyThread;

public class ThreadDemo01 {
	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程a");
		MyThread mt2 = new MyThread("线程b");
		new Thread(mt1).start();
		new Thread(mt2).start();
	} 
}

两种实现方式的区别和联系: 

    在程序开发中只要是多线程肯定永远以实现 Runnable 接口为主,因为实现 Runnable 接口相比继承 Thread 类有如下好处:

    · 避免点继承的局限,一个类可以继承多个接口。

    · 适用于资源的共享



以卖票程序为例,通过 Thread 类完成: 

package org.demo.dff;

class MyThread extends Thread {
	private int ticket=10;
	
	public void run() {
		for(int i = 0; i < 20; i++){
			if (this.ticket > 0) {
				System.out.println("賣票:ticket" + this.ticket--);
			}
		}
	} 
}; 

下面通过三个线程对象,同时卖票: 

package org.demo.dff; 

public class ThreadTicket {
	public static void main(String[] args) {
		MyThread mt1 = new MyThread();
		MyThread mt2 = new MyThread();
		MyThread mt3 = new MyThread();
		
		mt1.start();  //每个线程都各卖了10张,共卖了30张票
		mt2.start(); //但实际只有10张票,每个线程都卖自己的票
		mt3.start(); //没有达到资源共享
	}
}


如果使用 Runnable 就可以实现资源共享,看下面的例子: 

package org.demo.runnable; 

class MyThread implements Runnable {
	private int ticket = 10;
	
	public void run() {
		for (int i = 0; i < 20; i++) {
			if (this.ticket > 0) {
				System.out.println("賣票:ticket" + this.ticket--);
			}
		}
	}
} 

package org.demo.runnable; 

public class RunnableTicket {
	public static void main(String[] args) {
		MyThread mt = new MyThread();

		new Thread(mt).start(); //同一个mt,但是在Thread中就不可以,如果用同一
		new Thread(mt).start(); //个实例化对象mt,就会出现异常
		new Thread(mt).start();
	}
}; 

现在,虽然程序中有三个线程,但是一共卖了10张票,也就是说使用 Runnable 实现多线程可以达到资源共享目的。

查阅文档:

public class Thread implements Runnable

    可以发现,Thread 类实现了 Runnable 接口。



参考资料:

http://xuyuanshuaaa.iteye.com/blog/1109498

http://fdcwqmst.blog.163.com/blog/static/164061455201010581442985/





抱歉!评论已关闭.