Java 实现多线程的三种方式

  • 继承 Thread 类

  • 实现 Runnable 接口(Callable 类)

继承 Thread 类

Thread 类是一个支持多线程的功能类,只要有一个子类它就可以实现多线程的支持

public class MyThread extends Thread {// 多线程的操作类}

所有程序的起点是 main()方法,线程的起点是 run()方法。

在多线程的每个主体类中都要覆写 Thread 类中所提供的 run()方法。

public class MyThread extends Thread(){
    @Override
    public void run(){
        super.run();
    }
}

所有线程和进程是一样的,要轮流抢占资源,所以多线程执行应该是多个线程彼此交替执行。

如果直接调用 run()方法,不能直接启动多线程。

而是要使用 start()方法。(调用此方法执行的方法体是 run()方法定义的)

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
        	}
        }
    }

    private native void start0();

IllegalThreadStateException 是 RuntimeException 的子类,选择性处理。

如果一个线程对象重复启动,就会抛出此异常。

start0 方法与抽象方法类似,但是使用了 native 声明。(调用操作系统的 API)

所以此操作是 JVM 根据不同的操作系统实现。

实现 Runnable 接口

推荐使用这种方法。

虽然 Thread 类可以实现多线程的主体类定义,但是 Java 具有单继承局限,所以针对类的继承都应该回避。

多线程也一样。为了解决单继承的限制,专门实现了 Runnable 接口。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

接口中的所有方法都是 public

只需要让一个类实现 Runnable 接口、覆写 run()方法,就可以实现多线程。

但是和之前的区别就是,不能直接继承 start 方法。不能通过 start 方法启动。

不论任何情况,要想启动多线程,一定要依靠 Thread 类完成

在 Thread 类中有可以接受 Runnable 接口对象的 constructor

class MyThread implements Runnable{
    // 多线程的操作类
    private String name;
    public MyThread(String name){
        this.name = name;
    }
    @Override
    public void run() {
        for (int x = 0 ;x < 200; x++){
            System.out.println(this.name+"---->"+x);
        }
    }
}

public class Main{
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("线程A");
        MyThread mt2 = new MyThread("线程B");
        MyThread mt3 = new MyThread("线程C");
        new Thread(mt1).start();
        new Thread(mt2).start();
        new Thread(mt3).start();
    }
}

基本工作中基本都是实现 Runnable 接口。

多线程两种实现方式的区别(面试题)

1.首先明确,使用 Runnable 比 Thread,解决了单继承的局限性。

public class Thread implements Runnable {}

Thread 类实现了 Runnable 接口。(有 start 方法,就是一个线程对象,可以直接启动)

整个结构有点像代理设计模式。但是代理的话应该也调用 run 方法,但是使用的 start 方法。

2.使用 Runnable 比 Thread 可以更好的描述数据共享的概念。

此时的数据共享指的是多个线程访问同一资源的操作。

  • 如果使用 Thread:
class MyThread extends Thread{
    // 多线程的操作类
    private int ticket = 10;
    @Override
    public void run() {
        for (int x = 0 ;x < 200; x++){
            if (this.ticket > 0){
                System.out.println("卖票 ,ticket = " + this.ticket--);
            }
        }
    }
}

public class Main{
    public static void main(String[] args) {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();
        mt1.start();
        mt2.start();
        mt3.start();
    }
}

out:
卖票 ,ticket = 10
卖票 ,ticket = 9
卖票 ,ticket = 8
卖票 ,ticket = 7
卖票 ,ticket = 6
卖票 ,ticket = 5
卖票 ,ticket = 4
卖票 ,ticket = 3
卖票 ,ticket = 2
卖票 ,ticket = 1
卖票 ,ticket = 10
卖票 ,ticket = 9
卖票 ,ticket = 8
卖票 ,ticket = 7
卖票 ,ticket = 6
卖票 ,ticket = 5
卖票 ,ticket = 4
卖票 ,ticket = 3
卖票 ,ticket = 2
卖票 ,ticket = 1
卖票 ,ticket = 10
卖票 ,ticket = 9
卖票 ,ticket = 8
卖票 ,ticket = 7
卖票 ,ticket = 6
卖票 ,ticket = 5
卖票 ,ticket = 4
卖票 ,ticket = 3
卖票 ,ticket = 2
卖票 ,ticket = 1
Process finished with exit code 0
  • 如果使用 Runnable
class MyThread implements Runnable{
    // 多线程的操作类
    private int ticket = 10;
    @Override
    public void run() {
        for (int x = 0 ;x < 200; x++){
            if (this.ticket > 0){
                System.out.println("卖票 ,ticket = " + this.ticket--);
            }
        }
    }
}

public class Main{
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
    }
}

out:
卖票 ,ticket = 10
卖票 ,ticket = 9
卖票 ,ticket = 8
卖票 ,ticket = 7
卖票 ,ticket = 6
卖票 ,ticket = 5
卖票 ,ticket = 4
卖票 ,ticket = 3
卖票 ,ticket = 2
卖票 ,ticket = 1

Process finished with exit code 0

这样就实现了数据共享

多线程的第三种实现方式

Runnable 里面的 run 方法不能返回操作结果。

为了解决这样,提供了新的 Callable 接口。

在 java.util.concurrent 包中。

@FunctionalInterface
public interface Callable<V> {
    public V call() throws Exception;
}

返回结果的类型由 Callable 接口上的泛型来决定。

class MyThread implements Callable<String>{
    private int ticket = 10;

    @Override
    public String call() throws Exception {
        for (int x = 0 ; x <100 ; x++){
            if(this.ticket>0){
                System.out.println("卖票,ticket = "+ ticket--);
            }
        }
        return "票已经卖光";
    }
}

Thread 类中并没有 Callable 的构造方法,但是 jdk1.5 开始,有 java.util.concurrent.FutureTask类。

专门来负责 Callable 接口类的操作

public class FutureTask<V> implements RunnableFuture<V> {}
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

在 FutureTask 中有如下构造方法

public FutureTask(Callable<V> callable) {}

接受的目的只有一个,取得 call 方法的返回结果。

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        FutureTask<String> task1 = new FutureTask<>(mt1);
        FutureTask<String> task2 = new FutureTask<>(mt2);
        // 是Runnable的子类,可以使用Thread来启动
        new Thread(task1).start();
        new Thread(task2).start();
        // 通过get获取返回值
        System.out.println("A返回"+task1.get());
        System.out.println("B返回"+task2.get());
    }
}

out:
卖票,ticket = 10
卖票,ticket = 9
卖票,ticket = 8
卖票,ticket = 7
卖票,ticket = 6
卖票,ticket = 5
卖票,ticket = 4
卖票,ticket = 10
卖票,ticket = 3
卖票,ticket = 9
卖票,ticket = 2
卖票,ticket = 8
卖票,ticket = 1
卖票,ticket = 7
卖票,ticket = 6
卖票,ticket = 5
卖票,ticket = 4
卖票,ticket = 3
A返回票已经卖光
卖票,ticket = 2
卖票,ticket = 1
B返回票已经卖光

Process finished with exit code 0