David Dong

David Dong

Java/C/C#/Python

Java/C/C#/Python

POST

Java 的线程和 Runnable 接口

Java 实现多线程编程的方式有两种,一种是继承 Thread 类,另一种是实现 Runnable 接口。
下面的内容会分别介绍这两种用法以及使用上的区别。

Thread 类

Java 通过Thread类来实现多线程,关于 Thread 类的介绍,可以查阅 Java文档 Thread
Thread 类继承自java.lang.Object,实现了 Runnable 接口。
Thread 类的用法如下。
MyThreadClass.java

package com.daviddong;

public class MyThreadClass extends Thread {

    private int cnt = 0;
    
    @Override
    public void run() {
        // TODO Auto-generated method stub
        cnt++;
        System.out.println("线程" + Thread.currentThread().getName() + 
        " 被调用 " + cnt + "次");
    }

}

App.java

package com.daviddong;
import java.util.ArrayList;

public class App 
{

    public static void main( String[] args )
    {
        ArrayList<MyThreadClass> lists = new ArrayList<MyThreadClass>();
        for(int i=0; i<10; i++){
            MyThreadClass tmp = new MyThreadClass();
            lists.add(tmp);
        }
        for(MyThreadClass tmp:lists){
            tmp.start();
        }
    }

}

运行结果

线程Thread-0 被调用 1次
线程Thread-9 被调用 1次
线程Thread-8 被调用 1次
线程Thread-7 被调用 1次
线程Thread-5 被调用 1次
线程Thread-6 被调用 1次
线程Thread-3 被调用 1次
线程Thread-4 被调用 1次
线程Thread-2 被调用 1次
线程Thread-1 被调用 1次

由以上结果可以看出,线程的运行结果与执行顺序无关,并且10个线程分别被执行一次,线程内资源无法共享。

Runnable 接口

Runnable 接口的 Java 文档的介绍。Runnable Runnable 接口内只声明一个方法Run()的方法,同 Thread 内的Run()方法一样,该方法内定义了线程的执行体。
Runnable 接口的用法如下。
MyRunnable.java

package com.daviddong;

public class MyRunnable implements Runnable{

    private int cnt = 0;
    @Override
    public void run() {
        // TODO Auto-generated method stub
        cnt++;
        System.out.println("线程" + Thread.currentThread().getName() + 
        " 被调用 " + cnt + "次");
    }

}

App.java

package com.daviddong;
import java.util.ArrayList;

public class App 
{

    public static void main( String[] args )
    {
        MyRunnable mR = new MyRunnable();
        ArrayList<Thread> lists = new ArrayList<Thread>();
        for(int i=0; i<10; i++){
            Thread tmp = new Thread(mR);
            lists.add(tmp);
        }
        for(Thread tmp:lists){
            tmp.start();
        }   
    }

}

运行结果

线程Thread-1 被调用 2次
线程Thread-2 被调用 3次
线程Thread-0 被调用 2次
线程Thread-7 被调用 7次
线程Thread-6 被调用 6次
线程Thread-5 被调用 5次
线程Thread-4 被调用 4次
线程Thread-8 被调用 9次
线程Thread-9 被调用 9次
线程Thread-3 被调用 10次

同 Thread 的例子一样,看的出线程的执行顺序与调用顺序无关。但是因为不同的线程中传入的是同一个实现了 Runnable 接口的对象,因此内部的变量被共享。 由以上两个例子,可以看出使用 Thread 类和 Runnable 接口创建线程的不同主要是有2点。

  • 因为 Java 只能实现单继承,但可以实现多个接口,所以使用继承 Thread 类的方法就会导致无法继承其他的类,而 Runnable 接口就可以避免继承的局限。
  • 使用 Runnable 接口适合于资源的共享。

上面的程序还有一个问题,我们看到使用 Runable 接口时,虽然资源是被共享的,但是顺序不对,而且有重复出现的情况,正常应该是每个线程被调用时,调用次数加一,且应该顺序增加。造成这个问题的原因是因为不同的线程在执行都会访问共享的资源,而这些线程没有实现同步,共享资源会在一个线程调用的过程中被另外的线程改变。 解决这个问题的话就要用到synchronized,使线程之间同步运行。 我们修改一下代码。
MyRunnale.java

package com.daviddong;

public class MyRunnable implements Runnable{

    private int cnt = 0;
    @Override
    public synchronized void run() {
        // TODO Auto-generated method stub
        cnt++;
        System.out.println("线程" + Thread.currentThread().getName() + 
        " 被调用 " + cnt + "次");
    }

}

使用synchronized修饰run()方法,使得其在执行时与其他的线程同步。经过synchronized修饰后线程在执行run()方法时会确认是否有其他线程正在执行,如果有的话就加入队列等待,知道可以获得执行权。 执行结果如下。

线程Thread-1 被调用 1次
线程Thread-3 被调用 2次
线程Thread-2 被调用 3次
线程Thread-7 被调用 4次
线程Thread-0 被调用 5次
线程Thread-9 被调用 6次
线程Thread-8 被调用 7次
线程Thread-6 被调用 8次
线程Thread-5 被调用 9次
线程Thread-4 被调用 10次

现在我们看到共享资源是按顺序被调用且没有重复。
最后在介绍一些 Thread 类的常用方法。

Return Function Description
Thread currentThread 返回代码段当前被调用的线程
void sleep() 在指定的毫秒数内让当前”正在执行的线程”休眠(暂停执行)
void start() 启动当前线程
boolean isAlive() 当前线程是否处于激活状态
String getName() 返回当前线程的名字
long getID() 返回当前线程的ID
void setPriority() 设置优先级
boolean isDaemon() 是否为守护进程

Java

相关文章

继续阅读