首页 > 编程语言 >Java-Java基础学习(3)-多线程(1)

Java-Java基础学习(3)-多线程(1)

时间:2024-03-20 23:59:13浏览次数:29  
标签:Callable Java 对象 代理 学习 线程 println 多线程 public

Java-Java基础学习(4)-多线程(1)

3.多线程

在Java中,多线程主要的实现方式有四种:继承Thread类、实现Runnable接口、实现Callable接口经过FutureTask包装器来建立Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。

3.1. 四种创建方式

  • Thread class ==> 继承Thread类
  • Runnable接口 ==> 实现Runnable接口
  • Callable接口 ==> 实现Callable接口
  • 使用ExecutorService、Callable、Future实现有返回结果的线程(线程池方式)

3.2. Thread类

  1. 步骤

    • 自定义线程类继承Thread类
    • 重写run()方法,编写线程执行体
    • 创建线程对象,调用start()方法启动线程
  2. Thread类测试代码

    package com.hzs.basic.multithread;
    
    /**
     * @author Cherist Huan
     * @version 1.0
     */
    
    // 创建线程一:继承Thread类;实现run方法;start开启
    public class TestThread01 extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 200; i++) {
                System.out.println("子线程体:"+i);
            }
        }
    
        public static void main(String[] args) {
            TestThread01 testThread01 = new TestThread01();
            testThread01.start();
            //testThread01.run();
    
            for (int i = 0; i < 1000; i++) {
                System.out.println("主线程main:"+i);
            }
        }
    

    }

    输出:随机交替出现
    主线程main:101
    主线程main:102
    子线程体:0

  3. 注意

    • 线程start开启,不一定立即执行,由CPU调度执行

3.3. Runnable接口

  1. 步骤

    • 实现Runnabel接口,重写run方法;
    • new一个对象;
    • 丢入Thread对象中
  2. 测试代码

    package com.hzs.basic.multithread;
    
    /**
     * @author Cherist Huan
     * @version 1.0
     */
    
    // 创建线程方式二:实现Runnable接口,重写run方法;new一个对象;丢入 Thread对象中;
    public class TestRunnable01 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 200; i++) {
                System.out.println("子线程体:"+i);
            }
        }
    
        public static void main(String[] args) {
            TestRunnable01 testRunnable01 = new TestRunnable01();
            new Thread(testRunnable01).start();
    
            for (int i = 0; i < 1000; i++) {
                System.out.println("主线程main:"+i);
            }
        }
    }
    
    结果:同上
    

3.4. Callable接口

  1. 步骤

    • 实现Callable接口,需要返回值类型
    • 重写call方法,需要抛出异常
    • 创建目标对象
    • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(3)
    • 提交执行:Future result1 = ser.submit(t1);
    • 获取结果:Boolean r1 = result1.get();
    • 关闭服务:ser.shutdownNow();
  2. 测试代码

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CreateThreadDemo3 {

    public static void main(String[] args) throws Exception {
        // 建立线程任务,lambada方式实现接口并实现call方法
        Callable<Integer> callable = () -> {
            System.out.println("线程任务开始执行了...");
            Thread.sleep(2000);
            return 1;
        };

        // 将任务封装为FutureTask
        FutureTask<Integer> task = new FutureTask<>(callable);

        // 开启线程,执行线程任务
        new Thread(task).start();

        // ====================
        // 这里是在线程启动以后,线程结果返回以前
        System.out.println("线程启动以后,线程结果返回以前...");
        // ====================

        // 随心所欲完毕以后,拿到线程的执行结果
        Integer result = task.get();
        System.out.println("主线程中拿到异步任务执行的结果为:" + result);

    }

}

输出:

线程启动以后,线程结果返回以前...
线程任务开始执行了...
主线程中拿到异步任务执行的结果为:1

3.5 使用ExecutorService、Callable、Future实现有返回结果的线程(线程池方式)

  1. ExecutorService、Callable、Future三个接口都是属于Executor框架。可返回值的任务必须实现Callable接口。经过ExecutorService执行Callable任务后,能够获取到一个Future的对象,在该对象上调用get()就能够获取到Callable任务返回的结果了。

注意:Future的get方法是阻塞的,即:线程无返回结果,get方法会一直等待。

2.测试代码

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CreateThreadDemo4 {
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("---- 主程序开始运行 ----");
        Date startTime = new Date();
        
        int taskSize = 5;
        // 建立一个线程池,Executors提供了建立各类类型线程池的方法,具体详情请自行查阅
        ExecutorService executorService = Executors.newFixedThreadPool(taskSize);
        
        // 建立多个有返回值的任务
        List<Future> futureList = new ArrayList<Future>();
        for (int i = 0; i < taskSize; i++) {
            Callable callable = new MyCallable(i);
            // 执行任务并获取Future对象
            Future future = executorService.submit(callable);
            futureList.add(future);
        }
        
        // 关闭线程池
        executorService.shutdown();

        // 获取全部并发任务的运行结果
        for (Future future : futureList) {
            // 从Future对象上获取任务的返回值,并输出到控制台
            System.out.println(">>> " + future.get().toString());
        }

        Date endTime = new Date();
        System.out.println("---- 主程序结束运行 ----,程序运行耗时【" + (endTime.getTime() - startTime.getTime()) + "毫秒】");
    }
}

class MyCallable implements Callable<Object> {
    private int taskNum;

    MyCallable(int taskNum) {
        this.taskNum = taskNum;
    }

    public Object call() throws Exception {
        System.out.println(">>> " + taskNum + " 线程任务启动");
        Date startTime = new Date();
        Thread.sleep(1000);
        Date endTime = new Date();
        long time = endTime.getTime() - startTime.getTime();
        System.out.println(">>> " + taskNum + " 线程任务终止");
        return taskNum + "线程任务返回运行结果, 当前任务耗时【" + time + "毫秒】";
    }
}

输出结果:

---- 主程序开始运行 ----
>>> 0 线程任务启动
>>> 1 线程任务启动
>>> 2 线程任务启动
>>> 3 线程任务启动
>>> 4 线程任务启动
>>> 0 线程任务终止
>>> 1 线程任务终止
>>> 0线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 1线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 4 线程任务终止
>>> 3 线程任务终止
>>> 2 线程任务终止
>>> 2线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 3线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 4线程任务返回运行结果, 当前任务耗时【1001毫秒】
---- 主程序结束运行 ----,程序运行耗时【1009毫秒】

3.6. 静态代理

  1. 静态代理:代理对象和真实对象都要实现同一个接口;

  2. 好处:代理对象可以做很多真实对象做不了的事情;真实对象专注做自己的事情;

  3. 测试

    package com.hzs.basic.multithread;
    /**
     * @author Cherist Huan
     * @version 1.0
     */
    
    // 静态代理:代理对象和真实对象都要实现同一个接口
    // 好处:代理对象可以做很多真实对象做不了的事情;真实对象专注做自己的事情
    public class StaticProxy {
        public static void main(String[] args) {
    
            new ProxyCompany(new You()).HappyMerry();
            new Thread(()-> System.out.println("I love you")).start();
        }
    
    }
    
    interface  Merry{
        void  HappyMerry();
    }
    
    class  You implements  Merry{
        @Override
        public void HappyMerry() {
            System.out.println("You Merry");
        }
    }
    
    class ProxyCompany implements Merry{
    
        private Merry target;
    
        public  ProxyCompany(Merry target){
            this.target = target;
        }
    
        @Override
        public void HappyMerry() {
            before();
            //System.out.println("ProxyCompany 代理结婚,结婚对象是You");
            this.target.HappyMerry();
            after();
        }
    
        public void before()
        {
            System.out.println("Before");
        }
    
        public  void after(){
            System.out.println("After");
        }
    }
    
    输出:
    Before
    You Merry
    After
    I love you
    
  4. 静态代理应用场景

​ Java静态代理的应用场景主要出现在需要对目标对象的功能进行扩展,但又不想直接修改目标对象的源代码时。

​ 静态代理要求目标对象和代理对象实现同一个业务接口,这样代理对象就可以作为目标对象的一个代理,在不改变目标对象源代码的情况下,通过代理对象添加一些额外的操作,以实现特定的功能。这种代理方式在编译期间就已经确定了代理类的代码,因此编写简单,易于理解和维护。

​ 然而,静态代理的一个主要缺陷是,如果需要代理多个类,那么就需要针对每个实际对象编写一个代理类,这会导致代理类的数量增加,并且在代理类和被代理类的方法增加时需要手动维护代理类的代码,因此代理过程可能会变得复杂且难以管理。

​ 尽管如此,静态代理在某些特定场景下仍然非常有用。例如,当需要对某个对象的访问进行控制,或者需要在调用对象的方法前后添加一些额外的逻辑(如日志记录、性能监控、事务管理等)时,可以使用静态代理。在这些场景下,静态代理可以通过在代理对象中添加额外的逻辑,实现对目标对象功能的增强和扩展,同时保持目标对象代码的稳定性和不变性。

请注意,虽然静态代理有其特定的应用场景,但在需要代理的类数量较多或代理逻辑较复杂的情况下,动态代理可能是一个更好的选择。动态代理可以在运行时动态生成代理类,使得客户端代码更加简洁和易于维护。

3.7 线程和进程区别总结

首先看一下线程与进程的区别总结和理解(在java面试或者android面试中经常会问到这些问题,搜集整理供学习使用):
1.CPU负责整个计算机的处理工作(它就像一个工厂);
2.一个CPU一次只能运行一个任务(当我们这个工厂的电或者原料不充足时,只能供一厂房运行),而进程代表该工厂的一个厂房,代表CPU所能运行的单个任务(CPU在任何时刻都只运行一个进程);
3.一个进程包含很多线程 (就像一个车间里有很多工人一样,而进程就是车间的工人);
4.线程共享车间的空间(就像我们在工作时,在这个车间我们可以来回走动,也可以更换我们的工作地点);
5.而在我们使用某些存储空间时,其他工人就得等我们使用完(比如我们在卫生间时,其他人必须等我们使用完他们才可以用)
6.为了防止其他人使用我们一般都会上锁(在火车上我们一般都会上锁,使用完后会给别人一个信号灯提示),这就是“锁”,防止多个线程公用同一个存储区域
7.有的房间也可以让多个人同时操作假如人数为M,但是当人数大于M时我们就必须禁止其他人进入这个区域,我们也会上锁(这就是我们要讲的多线程)

标签:Callable,Java,对象,代理,学习,线程,println,多线程,public
From: https://blog.csdn.net/donghuandong/article/details/136891999

相关文章

  • Java为什么是值传递?
    Java为什么是值传递?在我们调用方法的时候,通常会传递参数,那我们到底传递的是对象本身,还是仅仅是对象的拷贝对象呢?先搞懂两个概念,形参和实参形参和实参实参(实际参数,Arguments):用于传递给函数/方法的参数,必须有确定的值。形参(形式参数,Parameters):用于定义函数/方法,接收实参,不......
  • 【数据结构和算法初阶(C语言)】二叉树的顺序结构--堆的实现/堆排序/topk问题详解---二
     目录 ​编辑1.二叉树的顺序结构及实现1.1二叉树的顺序结构2堆的概念及结构3堆的实现3.1堆的代码定义3.2堆插入数据3.3打印堆数据3.4堆的数据的删除3.5获取根部数据3.6判断堆是否为空3.7堆的销毁 4.建堆以及堆排序 4.1堆排序---是一种选择排序4.2升......
  • java学习系列(四):面向对象
    一、面向过程和面向对象1、程序设计的思路●面向对象(具体的步骤)是软件开发中的一类编程风格、开发范式。●除了面向对象,还有面向过程、指令式编程和函数式编程。在所有的编程范式中,我们接触最多的还是面向过程和面向对象两种。●早期先有面向过程思想,随着软件规模的......
  • Kotlin,简直是 Java 的 Pro Max!(笔记3 进阶篇)
    目录拓展拓展函数拓展属性运算符重载operator高阶函数通过高阶函数,模拟实现标准函数apply内联函数inlinenoinlinecrossinline泛型泛型类泛型方法限定泛型类型模拟实现apply标准函数(泛型版)泛型高级特性回顾Java中的协变和逆变Kotlin的协变和逆变委托......
  • 9.JavaWeb& javaScript基础
    目录导语:一、JavaWeb概述二、JavaScript基础概念:功能:1.基本语法(1)与html结合方式(2)注释(3)数据类型(4)变量(5)运算符(6)流程控制语句:(7)JS特殊语法:案例:99乘法表2.基本对象(1)Function:函数(方法)对象(2)Array:数组对象(3)Boolean(4)Date:日期对象(5)Math:数学对象(6)Number(7)String(8......
  • Python 深度学习第二版(GPT 重译)(三)
    七、使用Keras:深入探讨本章涵盖使用Sequential类、功能API和模型子类创建Keras模型使用内置的Keras训练和评估循环使用Keras回调函数自定义训练使用TensorBoard监控训练和评估指标从头开始编写训练和评估循环您现在对Keras有了一些经验——您熟......
  • Python 深度学习第二版(GPT 重译)(四)
    九、高级计算机视觉深度学习本章涵盖计算机视觉的不同分支:图像分类、图像分割、目标检测现代卷积神经网络架构模式:残差连接、批量归一化、深度可分离卷积可视化和解释卷积神经网络学习的技术上一章通过简单模型(一堆Conv2D和MaxPooling2D层)和一个简单的用例(二进制图像......
  • Python 深度学习第二版(GPT 重译)(一)
    前言序言如果你拿起这本书,你可能已经意识到深度学习在最近对人工智能领域所代表的非凡进步。我们从几乎无法使用的计算机视觉和自然语言处理发展到了在你每天使用的产品中大规模部署的高性能系统。这一突然进步的后果几乎影响到了每一个行业。我们已经将深度学习应用于几乎每个......
  • Python 深度学习第二版(GPT 重译)(二)
    四、入门神经网络:分类和回归本章涵盖您的第一个真实世界机器学习工作流示例处理矢量数据上的分类问题处理矢量数据上的连续回归问题本章旨在帮助您开始使用神经网络解决实际问题。您将巩固从第二章和第三章中获得的知识,并将所学应用于三个新任务,涵盖神经网络的三种最......
  • 代码学习第24天----回溯算法
    随想录日记part24time:time:time:2024.03.10主......