IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java多线程编程——线程创建 -> 正文阅读

[Java知识库]Java多线程编程——线程创建

Java中的线程有三种创建方式:继承Thread类、实现Runnable接口和实现Callable接口。下面我们一一进行介绍。

一、继承Thread类

使用该方法创建线程有如下三步:

1. 自定义线程类继承Thread类;

2. 重写该类中的run()方法,编写线程执行体;

3. 创建线程对象,调用start()方法启动线程。

例如,我们创建3个线程,分别去寻找指定区间内的素数。

public class Main {
    public static void main(String[] args) {
        EnumeratePrimeNumber task1 = new EnumeratePrimeNumber(10000, 11000);
        EnumeratePrimeNumber task2 = new EnumeratePrimeNumber(20000, 21000);
        EnumeratePrimeNumber task3 = new EnumeratePrimeNumber(30000, 31000);

        task1.start();
        task2.start();
        task3.start();
    }
}

class EnumeratePrimeNumber extends Thread {
    private int start;
    private int end;

    public EnumeratePrimeNumber() {}

    public EnumeratePrimeNumber(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public void run() {
        for (int i = start; i < end; ++i) {
            boolean isPrime = true;
            for (int j = 2; j * j <= i; ++j) {
                if (i % j == 0) {
                    isPrime = false;
                    break;
                }
            }
            if (isPrime)
                System.out.println(i);
        }
    }
}

该程序的核心点有:

  • 语句
    class EnumeratePrimeNumber extends Thread
    定义了任务类EnumeratePrimeNumber,它继承了Thread类。
  • 语句
    @Override
    public void run() {...}

? ? ? ? 重写了Thread类里的run()方法,从而可以寻找素数。

  • 语句
    EnumeratePrimeNumber task1 = new EnumeratePrimeNumber(10000, 11000);
    EnumeratePrimeNumber task2 = new EnumeratePrimeNumber(20000, 21000);
    EnumeratePrimeNumber task3 = new EnumeratePrimeNumber(30000, 31000);

? ? ? ? 分别创建了3个线程对象,分别寻找10000~11000、20000~21000和30000~31000间的素数。

  • 语句
    task1.start();
    task2.start();
    task3.start();

????????调用start()方法启动这3个线程。

程序的运行结果如下所示:

10007
10009
30011
20011
30013
10037
10039
10061
30029
20021
30047
10067
10069
......

可以看到,程序的执行结果并非是按照10000~11000、20000~21000和30000~31000三个区间依次输出的,而是在这三个区间之间来回切换,但是每个区间内找到的素数依然还是有序的。

二、实现Runnable接口

使用该方法创建线程有如下三步:

1. 自定义线程类实现Runnable接口;

2. 重写该类中的run()方法,编写线程执行体;

3. 创建自定义线程类的对象,接着创建Thread线程类对象,并将自定义线程类的对象作为Thread线程类构造方法的传入参数,最后调用Thread线程类对象的start()方法启动线程。

具体的步骤其实和第一种线程创建方式类似,只是第一步不再是继承而是实现,第三步不是直接调用自定义线程类对象的start方法,而是通过Thread线程类对象进行代理。

依然以上面寻找素数的问题为例,使用Runnable接口的实现程序如下:

public class Main {
    public static void main(String[] args) {
        EnumeratePrimeNumber task1 = new EnumeratePrimeNumber(10000, 11000);
        EnumeratePrimeNumber task2 = new EnumeratePrimeNumber(20000, 21000);
        EnumeratePrimeNumber task3 = new EnumeratePrimeNumber(30000, 31000);

        // can also be
        //      Thread t1 = new Thread(task1);
        //      t1.start();
        new Thread(task1).start();
        new Thread(task2).start();
        new Thread(task3).start();
    }
}

class EnumeratePrimeNumber implements Runnable {
    private int start;
    private int end;

    public EnumeratePrimeNumber() {}

    public EnumeratePrimeNumber(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public void run() {
        for (int i = start; i < end; ++i) {
            boolean isPrime = true;
            for (int j = 2; j * j <= i; ++j) {
                if (i % j == 0) {
                    isPrime = false;
                    break;
                }
            }
            if (isPrime)
                System.out.println(i);
        }
    }
}

上面代码相比于之前的,最大的区别有:

  • 自定义任务类时不再继承Thread类,而是实现Runnable接口:
    class EnumeratePrimeNumber implements Runnable
  • 在启动线程时,需要先创建任务类的实例,并在创建Thread对象时作为参数传递,再调用start()方法启动:
    new Thread(task1).start();

由于Java只支持单继承,所以在实践中更推荐使用实现Runnable接口创建线程。除此之外,这样的方式还支持同一对象被多个线程所使用。例如,我们可以编写程序模拟“抢票”——由多个线程去使用同一资源。

public class Main {
    public static void main(String[] args) {
        SnapUp race = new SnapUp();

        new Thread(race, "Alice").start();
        new Thread(race, "Bob").start();
        new Thread(race, "Cathy").start();
    }
}

class SnapUp implements Runnable {
    private static int numberOfTicket = 10;

    @Override
    public void run() {
        while (true) {
            if (numberOfTicket > 0) {
                System.out.println(Thread.currentThread().getName() + " gets " + (11 - numberOfTicket) + " ticket");
                numberOfTicket--;
            } else {
                break;
            }
        }
    }
}

上述程序安排了3个线程去模拟3个人,抢购10张票,并输出抢购结果。运行结果如下:

Alice gets 1 ticket
Alice gets 2 ticket
Alice gets 3 ticket
Bob gets 1 ticket
Alice gets 4 ticket
Alice gets 6 ticket
Alice gets 7 ticket
Bob gets 5 ticket
Bob gets 9 ticket
Bob gets 10 ticket
Cathy gets 8 ticket
Alice gets 8 ticket

非常惊讶的是,第1张票被Alice和Bob两个人都抢到了,第8张票被Cathy和Alice两个人都抢到了,这显然是不正确的。专业上将其称之为多线程访问导致的数据不一致。

最后总结一下使用实现Runnable接口创建线程的好处:

1. 避免了Thread类单继承的局限,更加灵活方便;

2. 方便同一个对象被多个线程使用。

三、实现Callable接口

有时候我们希望返回每个线程的执行结果,或者希望线程可以抛出异常,那么我们可以使用实现Callable接口创建线程。

实现Callable接口来创建线程的步骤如下:

1.?自定义线程类实现Callable接口;

2. 重写该类中的call()方法,编写线程执行体,给出返回值并抛出异常;

3. 创建自定义线程类的对象,比如

? ? Task t = new Task();

4. 创建执行服务,比如

? ? ExecuteService es = Executors.newFixedThreadPool(3); // 这里创建了大小为3的定长线程池

5. 提交执行,比如

? ? Future<Integer> futureResult = es.submit(t); // 这里的返回值为Integer类

6. 获取结果,比如

? ? Integer result =?futureResult.get();

7. 关闭服务

? ? es.shutdownNow();

这里我们以并行求和为例,即利用多个线程分别求出arr[s] + arr[s+1] + ... + arr[e-2] + arr[e-1]的和(或者说下标s~e-1间所有数组元素之和)。

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        int[] arr = new int[30000];
        for (int i = 0; i < 30000; i++) {
            arr[i] = i;
        }

        Summation task1 = new Summation(arr, 0, 10000);
        Summation task2 = new Summation(arr, 10000, 20000);
        Summation task3 = new Summation(arr, 20000, 30000);

        ExecutorService es = Executors.newFixedThreadPool(3);

        Future<Integer> futureSum1 = es.submit(task1);
        Future<Integer> futureSum2 = es.submit(task2);
        Future<Integer> futureSum3 = es.submit(task3);

        int sum1 = futureSum1.get();
        int sum2 = futureSum2.get();
        int sum3 = futureSum3.get();

        System.out.println("Sum = " + (sum1 + sum2 + sum3));

        es.shutdown();
    }
}

class Summation implements Callable<Integer> {
    private int[] arr;
    private int start;
    private int end;

    public Summation() {}

    public Summation(int[] arr, int start, int end) {
        this.arr = arr;
        this.start = start;
        this.end = end;
    }

    @Override
    public Integer call() throws ArrayIndexOutOfBoundsException {
        int sum = 0;
        for (int i = start; i < end; ++i) {
            sum += arr[i];
        }
        System.out.println(Thread.currentThread().getName() + ": " + sum);
        return sum;
    }
}

上述程序的运行结果为

pool-1-thread-2: 149995000
pool-1-thread-3: 249995000
pool-1-thread-1: 49995000
Sum = 449985000

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-22 14:00:25  更:2021-07-22 14:01:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/3 19:24:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码