Java多线程详解+案例实现


程序.进程.线程

在操作系统中运行的程序就是进程,例如微信、IDE、QQ等(暴露年龄了🤐)

一个进程中可以存在多个线程,如播放一个视频时有声音、图像、文字等等


Process与Thread

  • 程序是指令和数据的有序集合,其本身没有任何运行的含义,只是一个静态的概念。
  • 进程是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位。
  • 通常在一个进程中可以包含若干个线程,一个进程中至少有一个线程(main),不然没有存在的意义。线程是CPU调度和执行的单位。

普通方法调用和多线程图解

在这里插入图片描述


线程的三种创建方式

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

一、继承Thread类的实现(※※)

实现步骤

如下,JDK帮助文档中关于Thread类的使用介绍
在这里插入图片描述
(PS:需要这个中文版JDK帮助文档的,可到网盘自行下载。网盘容易被墙,失效了可以评论让我分享)
网盘链接:https://pan.baidu.com/s/1tFMqo7B5Umd439nnU9LhyQ 提取码:s6c4

使用继承Thread类实现多线程,根据JDK帮助文档主要有以下三个步骤:

  1. 自定义一个类去继承Thread类
  2. 重写run() 方法,编写线程执行体。
  3. 创建线程对象,调用start() 方法启动线程

代码实现

package com.thread;

//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
public class TestThread1 extends Thread {
    @Override
    public void run() {
        //super.run();
        //run方法线程体
        for (int i = 0; i < 1000; i++) {
            System.out.println("run方法线程体----"+i);
        }

    }

    /**
     * 两个线程是同时执行的
     * 注意:线程开启不一定立即执行,由CPU调度执行
     */

    public static void main(String[] args) {
        //main方法,主线程

        //创建一个线程对象
        TestThread1 thread1 = new TestThread1();
        //调用start()方法开启线程
        thread1.start();


        for (int i = 0; i < 1000; i++) {
            System.out.println("main方法,主线程----"+i);
        }

    }


}

案例应用

多线程同步下载图片:利用多线程来下载网络图片。这里我使用的是apache的commons.io包中FileUtils文件工具类,使用FileUtils文件工具类下的copyURLToFile方法,接收一个url地址即可下载网络图片


注意: commons.io包需要去apache官网下载,然后将下载的jar包放入lib目录(包)中,并右击lib包,选择Add as Library将lib中的jar包添加到Library中
在这里插入图片描述
在这里插入图片描述


定义一个WebDownloader类作为下载器。如下,查看copyURLToFile()方法的源码实现可发现,copyURLToFile方法至少需要传入一个url地址参数,并且需要抛出一个IOException异常

在这里插入图片描述


在下载器中定义一个downloader方法,接收两个参数(url地址和文件名),并使用copyURLToFile来下载传入的地址中的文件

class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        //FileUtils工具类中的copyURLToFile方法,copy网页文件(URL地址变成文件)
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题!");
        }
    }

}

在TestThread2继承类中,定义两个私有属性,分别保存url地址和文件名,并用构造器给两个属性赋值。继承了Thread类,还需要在TestThread2类中重写run方法,在run方法中即可编写下载文件线程的执行体(调用下载器中的downloader方法下载文件)

然后,便可在main主线程中创建多个线程对象 ,调用对应的start()方法启动线程了~
(描述过于繁杂,也可以直接看代码中简洁的注释,我主要是想写博客时再加深一下自己的记忆🤣🤣)

//继承Thread类
public class TestThread2 extends Thread {

    private String url;//网络图片地址
    private String name;//保存的文件名

    //构造器(构造方法) Alt + Insert
    public TestThread2(String url, String name) {
        this.url = url;
        this.name = name;
    }

    //重写run方法
    @Override
    public void run() {//下载图片线程的执行体
        //新建WebDownloader(下载器)对象
        WebDownloader webDownloader = new WebDownloader();
        //调用webDownloader中的downloader方法并传入相应参数  下载文件
        webDownloader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
    }

    public static void main(String[] args) {
        //创建三个该线程对象       (传入构造方法的参数)
        TestThread2 Thread1 = new TestThread2("https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3173584241,3533290860&fm=26&gp=0.jpg","tupian1.jpg");
        TestThread2 Thread2 = new TestThread2("https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1906469856,4113625838&fm=26&gp=0.jpg","tupian2.jpg");
        TestThread2 Thread3 = new TestThread2("https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1208538952,1443328523&fm=26&gp=0.jpg","tupian3.jpg");

        //主函数中调用start()方法开启三个线程
        // 每次运行三个线程下载图片顺序都不一样(即启动线程不一定立即执行,由CPU安排调度)
        Thread1.start();
        Thread2.start();
        Thread3.start();
    }


}

完整代码实现
package com.thread;

//apache的commons.io包,FileUtils文件工具类
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

//练习Thread,实现多线程同步下载图片
//继承Thread类
public class TestThread2 extends Thread {

    private String url;//网络图片地址
    private String name;//保存的文件名

    //构造器(构造方法) Alt + Insert
    public TestThread2(String url, String name) {
        this.url = url;
        this.name = name;
    }

    //重写run方法
    @Override
    public void run() {//下载图片线程的执行体
        //新建WebDownloader(下载器)对象
        WebDownloader webDownloader = new WebDownloader();
        //调用webDownloader中的downloader方法并传入相应参数  下载文件
        webDownloader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
    }

    public static void main(String[] args) {
        //创建三个该线程对象       (传入构造方法的参数)
        TestThread2 Thread1 = new TestThread2("https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3173584241,3533290860&fm=26&gp=0.jpg","tupian1.jpg");
        TestThread2 Thread2 = new TestThread2("https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1906469856,4113625838&fm=26&gp=0.jpg","tupian2.jpg");
        TestThread2 Thread3 = new TestThread2("https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1208538952,1443328523&fm=26&gp=0.jpg","tupian3.jpg");

        //主函数中调用start()方法开启三个线程
        // 每次运行三个线程下载图片顺序都不一样(即启动线程不一定立即执行,由CPU安排调度)
        Thread1.start();
        Thread2.start();
        Thread3.start();
    }


}

//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        //FileUtils工具类中的copyURLToFile方法,copy网页文件(URL地址变成文件)
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题!");
        }
    }

}


二、实现Runnable接口(※※※)

如下,查看官方JDK帮助文档实现Runnable接口Thread类的使用两种方式实现多线程差别不大
在这里插入图片描述
概括就是:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法


代码实现

和继承Thread的差别只是implements Runnable和开启线程时需要丢入runnable接口的实现类 new Thread(testThread3).start();

package com.thread;

//创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
public class TestThread3 implements Runnable {
    @Override
    public void run() {
        //super.run();
        //run方法线程体
        for (int i = 0; i < 10; i++) {
            System.out.println("run方法线程体----"+i);
        }

    }

    /**
     * 两个线程是同时执行的
     * 注意:线程开启不一定立即执行,由CPU调度执行
     */

    public static void main(String[] args) {
        //创建runnable接口的实现类对象
        TestThread3 testThread3 = new TestThread3();

        //创建线程对象,通过线程对象来开启线程(代理)
        //Thread thread = new Thread(testThread3);//丢入rnnnable接口实现类
        //thread.start();
        //上面两句可简写为
        new Thread(testThread3).start();

        for (int i = 0; i < 10; i++) {
            System.out.println("main方法,主线程----"+i);
        }

    }
}


案例应用

如下,使用"继承Thread类"的同一个案例,只是两个地方有所不同
在这里插入图片描述
在这里插入图片描述



两种方式实现多线程小结

继承Thread类:

  • 子类继承Thread类具备多线程能力
  • 启动线程:子类对象 . start()
  • 不建议使用:避免OOP单继承的局限性

实现Runnable接口:

  • 实现接口Runnable具有多线程能力
  • 启动线程:传入目标对象+Thread对象 . start()
  • 推荐使用:避免了单继承的局限性,方便同一个对象被多个线程使用


拓展案例

多线程同时操作同一个对象
案例: 买火车票的例子,一共有10张火车票,三个人(三个线程)同时去抢这10张火车票,输出这三个人分别抢了哪几张票?


思路: 创建一个线程对象,实现Runnable接口,重写了run方法(线程体中:总数10张票,每拿一张票就减减,小于1就跳出循环),三个线程都在用这10张票,每个人都会进去拿


代码实现:

package com.thread;

//多线程同时操作同一个对象
//买火车票的例子

public class TestThread4 implements Runnable {

    //票数
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true){
            if (ticketNums<=0){
                break;
            }
            //模拟延时,否则CPU运行太快10张票全被一个线程拿了
            try {//需要捕获一个异常
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //Thread.currentThread().getName():获得当前执行线程的名字
            System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"张票");
        }
    }

    public static void main(String[] args) {

        TestThread4 ticket = new TestThread4();

        new Thread(ticket,"小明").start();
        new Thread(ticket,"老师").start();
        new Thread(ticket,"黄牛党").start();
    }

}


运行结果
在这里插入图片描述

这时会有一个问题,不同的线程拿到了同一张票


发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
这是并发问题,后面再出博客讲解这个



三、实现Callable接口

实现步骤:

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

实现Callable接口方法,用的很少不是重点,这里不在具体赘述了


案例应用

还是使用下载网络文件的案例,注意与继承Thread类实现Runnable接口两种方式的区别即可

package com.thread.callable;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

//线程创建方式三:实现Callable接口

/**
 * Callable的好处:
 * 1.可以定义返回值
 * 2.可以抛出异常
 */
public class TestCallable implements Callable<Boolean> {

    private String url;//网络图片地址
    private String name;//保存的文件名

    //构造器(构造方法) Alt + Insert
    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }

    //重写run方法
    @Override
    public Boolean call() {//下载图片线程的执行体
        WebDown webDown = new WebDown();
        webDown.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建三个该线程对象       (传入构造方法的参数)
        TestCallable T1 = new TestCallable("https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3173584241,3533290860&fm=26&gp=0.jpg","tupian1.jpg");
        TestCallable T2 = new TestCallable("https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1906469856,4113625838&fm=26&gp=0.jpg","tupian2.jpg");
        TestCallable T3 = new TestCallable("https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1208538952,1443328523&fm=26&gp=0.jpg","tupian3.jpg");

        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1 = ser.submit(T1);
        Future<Boolean> r2 = ser.submit(T2);
        Future<Boolean> r3 = ser.submit(T3);

        //获取结果
        Boolean rs1 = r1.get();
        Boolean rs2 = r2.get();
        Boolean rs3 = r3.get();

        //关闭服务
        ser.shutdownNow();

    }

}

//下载器
class WebDown{
    //下载方法
    public void downloader(String url,String name){
        //FileUtils工具类中的copyURLToFile方法,copy网页文件(URL地址变成文件)
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题!");
        }
    }

}


上期回顾

java中的接口定义与实现


关于博主

为了让结尾好看一点…🙄🙄🙄
今天就到这了,已经快凌晨十二点了🥱,再不休息我担心明天早上,又会出现一股神秘的东方力量让我赖床了!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值