Java并发编程、线程基础以及实战,模拟查询和统计学生考试成绩

目录

1 线程与进程的理解

2 Java天生就是多线程的

3 Java中对线程的支持。 

4 认识 FutureTask

5 实战,使用 FutureTask 模拟查询和统计学生考试成绩。

6 源代码下载和补充


1 线程与进程的理解

什么是进程?

        进程是程序运行资源分配的最小单位,当你运行一个程序的时候,你就启动了一个进程。

进程之间是相互独立的,互不干扰。

什么是线程?

        线程是CPU调度的最小单位。

        线程不能单独存在,线程必须被包含在一个进程中,进程与线程的关系是1对多。

        在你启动一个程序的时候,通常会启动一个程序进程,进程中拥有多个线程,线程之间相互协

作、共同执行一个应用程序。

        即使是单核CPU也支持多线程,CPU通过给每个线程分配CPU时间片来实现CPU时间片轮转。

时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感

觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。        

上下文切换

        CPU在从一个时间片段切换到另一个时间片段执行任务前,会保存上一个任务的状态,以便下一次

切换回这个任务时,可以加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换,频繁的

上下文切换会消耗大量的CPU资源。 

        并发编程的目的是为了让程序运行的更快,并不是创建线程越多越好,而是要结合cpu的性能合理的设计,

而且要考虑到上下文切换、资源限制等问题。

2 Java天生就是多线程的

        当一个Java程序执行main()方法后,会默认创建多个线程来工作。

        测试代码

/**
 * 打印java程序启动时线程堆栈信息
 */
public class DumpMain {

    public static void main(String[] args) {
        //Java 虚拟机线程系统的管理接口
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // main启动后的线程堆栈信息
        ThreadInfo[] threadInfos =
                threadMXBean.dumpAllThreads(false, false);
        // 遍历线程信息,打印线程ID和线程名称信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] "
                    + threadInfo.getThreadName());
        }
    }
}

打印信息

[5] Monitor Ctrl-Break                  // 监控Ctrl-Break中断信号的

[4] Signal Dispatcher                    // 分发处理发送给JVM信号的线程

[3] Finalizer                                  // 调用对象finalize方法的线程

[2] Reference Handler                  // 清除Reference的线程

[1] main                                        // 程序入口,主线程

3 Java中对线程的支持。 

a) Thread

        Thread是Java对线程的抽象。

        通过继承Thread类,并重写Run()方法来定义一个线程任务。

/**
 * 自定义线程类
 */
public class MyThread extends Thread{
    // 重写接口方法,任务逻辑
    @Override
    public void run() {
        System.out.println("我是自定义线程任务");
    }

    public static void main(String[] args) {
        // 创建线程对象
        MyThread myThread = new MyThread();
        // 启动线程
        myThread.start();
        // 获取线程Id
        System.out.println(myThread.getId());
        // 获取线程名字
        System.out.println(myThread.getName());
        // 获取线程优先级
        System.out.println(myThread.getPriority());
        // 是否为守护线程。
        // 守护线程,是指在程序运行的时,后台提供一种通用服务的线程。比如java的垃圾回收。
        // 守护线程在所有用户线程终止后自动终止。
        System.out.println(myThread.isDaemon());

    }
}

        只有执行了start()方法后,才实现了真正意义上的启动线程。

        Thread 实现了 Runnable 接口,Runnable 接口只有一个方法 run() 来实现任务(业务

逻辑)代码。

        线程的优先级和守护线程。

        java 中的线程优先级的范围是1~10,默认的优先级是5,“高优先级线程”会优先于“低

优先级线程”执行。

        

        守护线程,是指在程序运行的时,后台提供一种通用服务的线程。比如java的垃圾回收。

        守护线程在所有用户线程终止后自动终止。

        要设置守护线程,设置myThread.setDaemon(true); 设置守护线程必须在线程运行(调用start)

之前。

b) Runnable

        Runnable 是Java对任务(业务逻辑)的抽象。

        Runnable 接口代码

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

        有了Thread为什么还要设计Runnable呢?

        首先是Java设计者对两者的定义不同,Thread是Java对线程的抽象,而Runnable是对任务

(业务逻辑)的抽象,一般实现Runnable接口定义业务逻辑,然后交由Thread来执行。

        其次,Java是单继承的,如果你的类继承了Thread,就不能再继承别的类了。

        使用Runnable 的代码

/**
 * 实现Runnable来定义线程类
 */
public class MyRunnable implements Runnable{

    // 重写接口方法,任务逻辑
    @Override
    public void run() {
        System.out.println("我是自定义线程任务");
    }

    public static void main(String[] args) {
        // 创建任务(业务逻辑)对象
        MyRunnable myRunnable = new MyRunnable();
        // 创建 Thread 执行对象
        // 将任务交给 Thread 执行
        Thread thread = new Thread(myRunnable);
        // 启动线程
        thread.start();
    }
}

4 认识 FutureTask

        

        FutureTask 是 java 除了 Thread 和 Runnable 外支持的第三种线程类,它是有返回值的。

        首先看一下 FutureTask 的类图

 

        FutureTask 实现了Runnable接口 ,也就是说它可以作为任务交由Thread执行。

        再看 FutureTask 的一个构造函数

public FutureTask(Callable<V> callable) {
}

        它支持传入一个带泛型结果的 Callable 对象,FutureTask 会在run() 方法里调用 callable

的 call() 方法,所以我们可以创建一个Callable对象,重写call() 方法,然后在构造 FutureTask 对

象的时候传进去。

        这个 call() 方法就是任务(业务逻辑)的定义代码,并会返回任务结果V。

        实现一个自己的任务处理对象

// Callable 实现类 返回结果为 Integer
public class MyCallable implements Callable<Integer>{

    // 返回结果
    @Override
    public Integer call() throws Exception {
        // 业务处理,模拟返回结果
        return 1;
    }

    public static void main(String[] args) throws Exception {
        // 创建 Callable 对象
        MyCallable myCallable = new MyCallable();
        // 获取返回结果
        int result = myCallable.call();
        // 打印
        System.out.println(result);
    }
}

        单独实现一个 Callable 是没有什么意义的,一般结合 FutureTask 使用。

        那么 FutureTask 是如果获取返回结果呢?

        再看类图,FutureTask 同时实现了Future 接口,所以通过 get() 方法就可以返回执行结果。

这个get() 方法是阻塞方法,即 get() 之后的代码必须等到线程运行完毕后才会继续执行。

        用 FutureTask 执行任务代码

// 自定义 FutureTask
public class MyFutrueTask {

    public static void main(String[] args) {

        // 定义任务(业务逻辑)和返回值
        MyCallable callable = new MyCallable();
        // 以callable 为参数创建 FutureTask 对象
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // 交由 thread 执行
        Thread thread = new Thread(futureTask);
        // 启动线程
        thread.start();
        try {
            // 阻塞并获取结果
            Integer result = futureTask.get();
            // 打印结果
            System.out.println(result);
            // 捕获中断异常
        } catch (InterruptedException e) {
            // 打印异常信息
            e.printStackTrace();
            // 捕获 执行异常
        } catch (ExecutionException e) {
            // 打印异常信息
            e.printStackTrace();
        }

    }
}

        FutureTask 的一些常用方法

        // 任务是否被取消

        futureTask.isCancelled();

        // 取消任务执行

        futureTask.cancel(true);

        // 任务是否完成

        futureTask.isDone();

5 实战,使用 FutureTask 模拟查询和统计学生考试成绩。

设计要求

分别统计某学生的语文,数学和英语的分数并计算总分。

实现代码

/**
 * 学生考试分数查询任务类
 * 实现Callable,返回Integer
 */
public class StudentCall implements Callable<Integer> {

    // 科目名字
    private String courseName;

    // 带科目名字的构造参数
    public StudentCall(String courseName) {
        this.courseName = courseName;
    }

    // 重写任务(业务逻辑)方法 查询科目考试分数
    @Override
    public Integer call() throws Exception {

        // 开始时间,毫秒
        long startTimeMillis = System.currentTimeMillis();

        // 模拟查询科目耗时操作 0~4秒
        int sleepMillis = (int)(Math.random()*5)*1000;
        Thread.sleep(sleepMillis);

        // 任务结束时间
        System.out.println(courseName+"任务结束时间:"+
                new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss:SSS") .format(new Date()));

        // 结束时间,毫秒
        long endTimeMillis = System.currentTimeMillis();

        // 耗时
        System.out.println(courseName+"任务耗时:"+(endTimeMillis - startTimeMillis)+"ms");

        // 模拟返回查询科目分数
        int score = (int)(Math.random()*100);

        // 返回结果
        return score;
    }
}
/**
 * FutureTask 实现
 * 并发查询学生各科成绩和总分
 */
public class MyFutrue4Score {

    // 模拟查询科目
    public static String[] courseNames = {"语文","数学","英语"};

    public static void main(String[] args) {

        // 开始时间,毫秒
        long startTimeMillis = System.currentTimeMillis();
        
        // 总分
        int sumScore = 0;

        // 保存查询科目分数任务
        List<FutureTask<Integer>> scoreList = new ArrayList<>();

        // 遍历科目
        for(String courseName : courseNames){
            // 创建查询科目分数任务
            StudentCall call = new StudentCall(courseName);
            // 创建 FutureTask,传入 Callable 对象
            FutureTask<Integer>  futureTask = new FutureTask<>(call);
            // 将 futureTask 添加到 List 容器中供以后使用
            scoreList.add(futureTask);
            // 交由 Thread 执行
            new Thread(futureTask).start();
        }

        // 遍历任务
        for(int i=0;i<scoreList.size();i++){
            try {
                // 调用 futureTask 的阻塞方法,获取任务结果
                int singleScore = scoreList.get(i).get();
                // 打印科目分数
                System.out.println(courseNames[i]+":"+singleScore);
                // 计算总分数
                sumScore = sumScore + singleScore;
                // 捕获异常并打印异常信息
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        // 打印总分数,必须在所有任务执行完毕后才会输出
        System.out.println("总分:"+sumScore);

        // 结束时间,毫秒
        long endTimeMillis = System.currentTimeMillis();

        // 耗时
        System.out.println("任务总耗时:"+(endTimeMillis - startTimeMillis)+"ms");

    }
}

 

执行结果:

        数学任务结束时间:2023-06-15-17:14:42:447

        数学任务耗时:2023ms

        英语任务结束时间:2023-06-15-17:14:43:430

        英语任务耗时:3006ms

        语文任务结束时间:2023-06-15-17:14:43:431

        语文任务耗时:3008ms

        语文:17

        数学:24

        英语:91

        总分:132

        任务总耗时:3008ms

运行时间分析

        虽然使用了多个线程同时查看学生每门功课分数,但是程序总耗时是由其中最耗时的线程

执行时间而决定的。

6 源代码下载和补充

        源码下载地址 gitcode

        部署前请先查看readme.txt说明文档。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值