Java多线程实现

导语

Java是一门为数不多的多线程支持的编程语言。

主要内容

  • 掌握Java中三种多线程的实现方式

具体内容

如果想在Java之中实现多线程有两种途径:
- 继承Thread类。
- 实现Runnable接口(Callable接口)

继承Thread类

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

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

}

public class TestDemo {  // 主类
    public static void main(String args[]) {

    }
}

所有程序的起点是main()方法,但是所有线程也一定要有一个自己的起点,那么这个起点就是run()方法,也就是说在多线程的每个主体类之中都必须覆写Thread类中所提供的run()方法。

public void run() {}

这个方法上没有返回值,线程一旦开始就要一直执行,不能够返回内容。

// 线程操作主类
public class MyThread extends Thread {  // 这就是一个多线程的操作类
    private String name;
    public MyThread(String name) {  // 定义构造方法
        this.name = name;
    }
    @Override
    public void run() {  // 覆写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();
    }
}

输出结果

线程A-->0
线程A-->1
...
线程A-->198
线程A-->199
线程B-->0
线程B-->1
...
线程B-->198
线程B-->199
线程C-->0
线程C-->1
...
线程C-->198
线程C-->199

本线程类的功能是进行循环的输出操作,所有的线程与进程是一样的,都必须轮流去抢占资源,所以多线程的执行应该是多个线程彼此交替执行,也就是说如果直接调用了run()方法,那么并不能够启动多线程,多线程的启动的唯一方法就是Thread类中的start()方法:public void start() (调用此方法执行的方法体是run()方法定义的)。

修改代码

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.start();
        mt2.start();
        mt3.start();
    }
}

输出结果

线程A-->0
线程A-->1
线程C-->0
线程B-->0
线程C-->1
线程B-->1
线程A-->2
...

此时每一个线程对象交替执行。

观察Thread的源代码

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

        }
    }
}

private native static void nativeCreate(Thread t, long stackSize, boolean daemon);

但是每个线程只能执行一次start()方法,否则会抛出IllegalThreadStateException异常。
发现在start()方法里面要调用一个nativeCreate()方法,而且此方法的结构与抽象方法类似,使用了native声明,在Java的开发里面有一门技术称为JNI技术(Java Native Interface),这门技术的特点:使用Java调用本机操作系统提供的函数。但是这样的技术有一个缺点,不能够离开特定的操作系统。
如果想要线程能够执行,需要操作系统来进行资源分配,所以此操作严格来讲主要是由JVM负责根据不同的操作系统而实现的。
即:使用Thread类的start()方法不仅仅要启动多线程的执行代码,还要去根据不同的操作系统进行资源分配。

实现Runnable接口

虽然Thread类可以实现多线程的主体类定义,但是它有一个问题,Java具有单继承局限,正因为如此在任何情况下针对于类的继承都应该是回避的问题,那么多线程也一样,为了解决单继承的限制,在Java里面专门提供了Runnable接口,此接口定义如下。

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

那么只需要让一个类实现Runnable接口即可,并且也需要覆写run()方法。

public class MyThread implements Runnable {  // 这就是一个多线程的操作类
    private String name;
    public MyThread(String name) {  // 定义构造方法
        this.name = name;
    }
    @Override
    public void run() {  // 覆写run()方法,作炎线程的主体操作方法
        for(int i = 0; i < 200; i++) {
            System.out.println(this.name + "-->" + i);
        }
    }
}

与继承Thread类相比,此时的MyThread类在结构上与之前 是没有区别的,但是有一点是有严重区别的,如果此时继承了Thread类,那么可以直接继承start()方法,但是如果实现的是Runnbale接口,并没有start()方法可以被继承。

不管何种情况下,如果想要启动多线程一定依靠Thread类完成,在Thread类里面定义有以下的构造方法:public Thread(Runnable target),接收的是Runnable接口对象。

启动多线程:

public class TestDemo {  // 主类
    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接口是最合适的。

多线程两种实现方法的区别

通过讲解已经清楚了多线程的两种实现方式,那么这两种方式有哪些区别呢?
首先一定要明确的是,使用Runnable接口与Thread类相比,解决了单继承的定义局限,所以不管后面的区别与联系是什么,至少这 一点上就能发现,Runnable接口更优。

首先观察一下Thread类的定义。

public class Thread implements Runnable {}

发现Thread类实现了Runnable接口,那么这样一来程序就变为了以下的形式。

定义结构

此时,整个的定义结构看起来非常像代理设计模式,如果是代理设计模式,客户端调用的代理类的方法也应该是接口里提供的方法,那么也应该是run()才对(当时技术不成熟)。
除了以上的联系之外,还有一点:使用Runnable接口可以比Thread类能够更好的描述数据共享这一概念。此时的数据共享指的是多个线程访问同一资源的操作。

范例:观察代码(每一个线程对象都必须通过start()启动)

public class MyThread extends Thread {
    private int ticket = 10;
    @Override
    public void run() {  // 覆写run()方法,作炎线程的主体操作方法
        for(int i = 0; i < 100; i++) {
            if(this.ticket > 0) {
                System.out.println("卖票,ticket = " + this.ticket--);
            }
        }
    }
}

public class TestDemo {  // 主类
    public static void main(String args[]) {
        // 由于MyThread类有start()方法,所以每一个MyThread类对象就是一个线程对象,可以直接启动
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();

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

输出结果

卖票,ticket = 10
卖票,ticket = 10
卖票,ticket = 9
卖票,ticket = 10
...

本程序声明了三个MyThread类对象,并且分别调用了三次start()方法,启动线程对象。但是最终的结果发现每一个线程对象都在卖各自的10张票,因为此时产生了三个线程对象,此时并不存在有数据共享这一概念。

范例:利用Runnable来实现

public class MyThread implements Runnable {
    private int ticket = 10;
    @Override
    public void run() {  // 覆写run()方法,作炎线程的主体操作方法
        for(int i = 0; i < 100; i++) {
            if(this.ticket > 0) {
                System.out.println("卖票,ticket = " + this.ticket--);
            }
        }
    }
}

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

输出结果

卖票,ticket = 10
卖票,ticket = 9
卖票,ticket = 8
卖票,ticket = 7
...

此时也属于三个线程对象,可是唯一的区别是,这三个线程对象都直接占用了同一个MyThread类的对象引用,也就是说这三个线程对象都直接访问同一个数据资源。

Thread类与Runnable接口实现多线程的区别:
- Thread类是Runnable接口的子类,使用Runnable接口实现多线程可以避免单继承局限。
- Runnable接口实现的多线程可以比Thread类实现的多线程更加清楚的描述了数据共享的概念。

第三种实现方式(理解)

使用Runnable接口实现的多线程可以避免单继承局限,但是有一个问题,Runnable接口里面的run()方法,不能返回操作结果。为了解决这样的矛盾,提供了一个新的接口java.util.conurrent.Callable接口。

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

call()方法执行完线程的主题功能之后可以返回一个结果,而返回结果的类型由Callable接口上的泛型来决定。

范例:定义一个线程主体类

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

    @Override
    public String call() throws Exception {
        for(int i = 0; i < 100; i++) {
            if(this.ticket > 0) {
                System.out.println("卖票,ticket = " + this.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> {}

在FutureTask类里面定义有如下的构造方法:

public FutureTask(Callable<V> callable) {}

接收Callable对象的目的只有一个,那么就是取得call()方法的返回结果。
启动线程的代码如下。

public class TestDemo {
    public static void main(String args[]) throws Exception {
        MyThread mt = new MyThread();
        FutureTask<String> task = new FutureTask(mt);  // 目的是为了取得call()返回结果,记得设置返回结果类型
        // FutureTask是Runnable接口子类,所以可以使用Thread类的构造来接收task对象
        new Thread(task).start();  // 启动多线程
        // 多线程执行完毕后可以取得内容,依靠FutureTask的父接口Future中的get()方法完成
        System.out.println("线程的返回结果:" + task.get();
    }
}

输出结果

卖票,ticket = 10
卖票,ticket = 9
卖票,ticket = 8
...
卖票,ticket = 1
票已卖光!

最麻烦的问题在于需要接收返回值信息,并且又要与原始的多线程(Thread类)的实现靠拢。

总结

  • 对于多线程的实现,重点在于Runnable接口与Thread类启动的配合上。
  • 对于JDK1.5新特性,Callable区别就在于返回值的实现。

进入我的CSDN戳这里(我的博客导航)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值