Java 多线程的学习

Posted by 阳阳丶Sheep on 04-07,2020

Java 中多线程的实现方式 ( JDK1.5之后增加了第三种 )

实现多线程的方式

  • 继承 Thread 类
  • 实现 Runnable 接口 ( Callable 接口 )

继承 Thread 类

// 线程操作主类
// 这就是一个多线程操作类
class MyThread extends Thread {
}

注意:在多线程的每个主体类中都必须覆写 Thread 类中提供的 run() 方法。同时因为 run() 方法没有返回值,所以代表线程一旦开启,就无法停止,需要一直执行下去。

@Override
public void run() {
    super.run();
}

Demo

class MyThread extends Thread {
    private String name;
    public MyThread(String name) {
	this.name = name;
    }
    @Override
    public void run() {
	for (int i = 0; i < 200; i++) {
	    System.out.println(this.name + " --> " + i);
	}
    }
}

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

	mt1.run();
	mt2.run();
	mt3.run();
    }
}

由此,我们创建了一个主类以及一个线程的主类,线程的主类继承了Thread,从而实现了线程的操作。

run()方法中我们实现了一个功能,循环200次输出一行文字。并且在主类中通过创建对象的方式,创建了三个对应的线程。

但是通过控制台的输出我们发现,效果并不是我们想要的。我们想要的是三个线程互相争夺资源从而输出,而实际上是三个线程依次输出。所以我们这样写的是有问题的。

实际上,之所以无法出现预计出现的效果,是因为我们不能用run()方法开启线程,我们需要用start()方法开启线程。而start()方法执行的内容便是run()方法中写的内容。

所以我们需要把main方法中的语句修改成:

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

    mt1.start();
    mt2.start();
    mt3.start();
}

由此,多线程的第一个例子就搞定了。不过由于现在的电脑的配置都比较不错,所以程序运行都是一瞬间的事情。有条件的可以找台比较老旧的电脑测试一下。但是在这个例子中也是可以看到一点端倪的。

当然,如果看过多线程的源码就会看到。如果一个线程重复启动的话,就会抛出一个异常IllegalThreadStateException

实现Runnable接口

上面聊了聊 Java 实现多线程的一种方式 - 继承Thread类。但是这种方式是有比较大的问题的。为什么呢?因为在 Java 中,类是单继承的,不能继承多个类。所以这就导致了一定的局限性,那么不管是多线程也好,还是其他的点也好,都是需要规避的一个地方。而这部分要聊的是实现Runnable接口。在 Java 中一个类不能继承多个类,但是可以实现多个接口,相对于继承Thread,这种方式就没有那么大的局限性了。

那么首先看如何实现Runnable接口

class MyThread implements Runnable {
    private String name;
    public MyThread(String name) {
	this.name = name;
    }
    @Override
    public void run() {
	for (int i = 0; i < 200; i++) {
	    System.out.println(this.name + " --> " + i);
	}
    }
}

看到这里会发现,实现Runnable接口与继承Thread类的部分代码是基本上差不多的,没啥太大的区别。实际上也确实是这样。但是有一点区别很大的是,继承Thread类会提供一个start()方法用于开启线程,但是实现了Runnable接口的话,这个start()方法是没有的。

那么如何开启线程呢?还是需要Thread类去开启线程。因为不管如何,开启线程都只能用Thread类,区别就是使用什么样的方式罢了。

Thread中有一个构造方法,叫public Thread(Runnable target),这个方法接收的是Runnable类型的对象,也就是我们自定义的线程类实现的类的类型。那么我们就可以利用这个构造方法去开启线程。

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();
}

那么,通过上面的方式,我们就可以开启线程了。

面试题:请解释Thread类与Runnable接口实现多线程的区别
(其他问法:请解释多线程两种实现方式的区别):

  • Thread类是Runnable接口的子类,使用Runnable接口实现多线程可以避免单继承局限;
  • Runnable接口实现的多线程可以比Thread类实现的多线程更加清楚地描述数据共享的概念;

面试题:请写出多线程两种实现操作:

答案:把上面文章中列出的Thread类继承的方式和Runnable接口实现方式的代码都写出来。

实现Callable接口

Java 中开启线程的第三个方法就是实现Callable接口,但是我们会发现在Thread类中并没有直接支持Callable接口的多线程方法。

但是从JDK1.5开始,Java提供了FutureTack<V>类,位于java.util.concurrent包中,这个类主要负责的就是Callable接口对象操作的。

FutureTack<V>这个类中有一个构造方法:public FutureTack(Callable<V> callable),专门接收Callable接口对象。那么接收的目的就只有一个,就是取得call()方法的返回值结果。

而另一方面,FutureTack类也是Runnable接口的子类,所以同样可以通过Thread去调用开启。

那么我们可以上面第二个开启线程的方法的代码改为下面这样的:

public static void main(String[] arg) {
    MyThread mt1 = new MyTread();
    MyThread mt2 = new MyTread();

    FutureTask<String> task1 = new FutureTask<String>(mt1);
    FutureTask<String> task2 = new FutureTask<String>(mt2);

    // FutureTask 是 Runnable 接口的子类,所以可以使用 Thread 类的构造来接收 task 对象
    new Thread(task1).start();
    new Thread(task2).start();
}