【Java专栏连载】 多线程详解 (含面试题总结(未完待续))

发布于:2023-01-22 ⋅ 阅读:(10) ⋅ 点赞:(0) ⋅ 评论:(0)

✅ 个人整理的对应知识点的面试题
👉 点击目录可跳转
🏆 信念: 种一棵树,最好的时间就是现在! 共勉!🤝

🔌🔌通过总结、梳理,给自己充电!!!加油!

✅ 前言:我们都知道计算机的核心是CPU,它承担了所有的计算任务,而操作系统是计算机的管理者,它负责任务的调度,资源的分配和管理,统领整个计算机硬件;应用程序是具有某种功能的程序,而程序是运行于操作系统之上的。

1. 👉基本概念

1.1 程序

为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。

1.2 进程与线程 ✅

进程:程序运行和资源分配的基本单位,进程在执行过程中拥有独立的内存单元,而线程进行划分的尺度一般要小很多,多个线程共享内存资源,减少了切换次数,这就导致了多线程的一些程序能够出现更高的并发性(效率更高)。一个程序都必须要有一个以上的进程,而相对于一个进程而言,也必须要要有一个以上的线程

线程:线程是CPU调度和分派的基本单位,它是进程的一部分,只能由进程创建,是进程的一个实体,同一进程中的多个线程之间可以并发执行。

1.3 进程与线程的区别 ✅

  • 区别
  • 根本:进程是程序运行和资源分配的基本单元,而线程是处理器任务调度的和执行的基本单位。
  • 关系:包含关系,线程是进程的一部分,可以把线程看作是轻量级的进程,一个进程内的线程是并发执行的。
  • 内存分配:进程之间地址空间和资源独立;同一进程的线程共享本地的地址空间和资源。
  • 开销:进程的创建和撤销,操作系统相应地要进行分配和回收资源,这远大于线程的创建和撤销时的开销。

1.4 并发和并行 ✅

  • 并发
  • 多个事件都处于同一个处理机上,同一个时间间隔内发生。
  • 并行
  • 多处理器上的程序才可实现并行处理,即不同实体上的多个事件同时发生。或者说,同时发生的多个并发事件;但并发不一定并行,并发事件之间不一定要同一时刻发生。

1.5 同步、异步、互斥 ✅

  • 同步
  • 同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。
  • 异步
  • 异步和同步是相对的,异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。
  • 互斥
  • 进程之间相互排斥的使用临界资源的现象。

2. 👉多线程

2.1 基本概念

多线程:多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码,多线程可以实现线程间的切换执行。是异步的一种实现方式,一种手段,它是最终目的。

2.2 创建线程的方式?✅

  • 继承Thread类创建线程
  • 实现 Runnable 接口创建线程
  • 通过 Callable 和 Future 创建线程
  • 通过线程池创建线程

2.2.1 继承Thread类创建线程

public class ThreadDemo extends Thread{
    @Override
    public void run() {
        //run方法的线程体
        for (int i = 0; i < 25; i++) {
            System.out.println("正在运行"+i);
        }
    }

    public static void main(String[] args) {
        //主线程 main
        //创建一个线程对象
        ThreadDemo threadDemo = new ThreadDemo();
        //调用strat()方法开启线程,交替执行的
        //若调用run方法,则是按顺序执行
        threadDemo.start();
//      threadDemo.run();主线程
        for (int i = 0; i < 100; i++) {
            System.out.println("多线程案例"+i);
        }
    }
}

运行结果:

请添加图片描述
线程是同时执行的,每次执行的顺序不一定相同,由CPU安排调度的

小案例:

练习Thread,实现多线程同步下载图片,导入了一个common IO的jar包 这里面的图片链接可以自己更换

在这里插入图片描述

代码实现:

import org.apache.commons.io.FileUtils;

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

//练习Thread,实现多线程同步下载图片    导入了一个common IO的jar包
public class ThreadDemo02 extends Thread {
    private String url;
    private String name;
    //构造方法 初始化
    public ThreadDemo02(String url,String name){
        this.url = url;
        this.name = name;
    }
//run方法是线程的执行体 ,重写run()方法
    @Override
    public void run() {
        //创建下载器对象
        WebDownloader webDownloader = new WebDownloader();
        //调用下载方法
        webDownloader.downloader(url,name);
        System.out.println("下载了图片,文件名为:"+name);
    }

    //主线程
    public static void main(String[] args) {
        //创建线程对象
        ThreadDemo02 t1 = new ThreadDemo02("\thttps://img-blog.csdnimg.cn/img_convert/4a295f7dd5255538958201ce0e637d11.png","jori1.png");
        ThreadDemo02 t2 = new ThreadDemo02("\thttps://img-blog.csdnimg.cn/img_convert/88c9e642358704ad7a568169861af2ce.png","jori2.png");
        ThreadDemo02 t3 = new ThreadDemo02("\thttps://img-blog.csdnimg.cn/img_convert/20fb1225302c1c4070e929a6ab7f755e.png","jori3.png");
       //线程是同时执行的,每次执行的顺序不一定相同,由CPU安排调度的
        t1.start();
        t3.start();
        t2.start();

    }
}

//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){

        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));//把url保存到文件
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("iO异常,downloader方法出现问题!");

        }
    }
}

运行结果:
请添加图片描述

2.2.2 实现 Runnable 接口创建线程

//创建线程方式2 实现runnable接口
public class RunnableDemo implements Runnable{
//    重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 25; i++) {
            System.out.println("正在运行中"+i);
        }
    }
//main主线程
    public static void main(String[] args) {
//思路:执行线程先创建runnable实现类的对象,继续创建一个Thread对象,放入runnable接口实现类,再调用start方法
        RunnableDemo runnableDemo = new RunnableDemo();//先创建runnable实现类的对象
        Thread thread = new Thread(runnableDemo);//继续创建一个Thread对象,放入runnable接口实现类
        thread.start();//再调用start方法
        /*以上两行代码可以简写为:
        new Thread(runnableDemo).start();
         */

        for (int i = 0; i < 100; i++) {
            System.out.println("多线程"+i);
        }

    }
}

练习Thread,实现多线程同步下载图片 这次采用实现Runnable接口

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;

/*练习Thread,实现多线程同步下载图片 这次采用实现Runnable接口,推荐用这种方式,因为采用Thread
的话,是单继承的,有局限性,而实现接口,可以实现多个接口*/

public class RunnableDemo02 implements Runnable {
    private String url;
    private String name;
    //构造方法 初始化
    public RunnableDemo02(String url,String name){
        this.url = url;
        this.name = name;
    }
    //run方法是线程的执行体 ,重写run()方法
    @Override
    public void run() {
        //创建下载器对象
        WebDownloader webDownloader = new WebDownloader();
        //调用下载方法
        webDownloader.downloader(url,name);
        System.out.println("下载了图片,文件名为:"+name);
    }

    //main()主线程
    public static void main(String[] args) {
        //先创建runnable实现类的对象,构造方法初始化
        RunnableDemo02 r1 = new RunnableDemo02("\thttps://img-blog.csdnimg.cn/img_convert/4a295f7dd5255538958201ce0e637d11.png","jori1.png");
        RunnableDemo02 r2 = new RunnableDemo02("\thttps://img-blog.csdnimg.cn/img_convert/88c9e642358704ad7a568169861af2ce.png","jori2.png");
        RunnableDemo02 r3 = new RunnableDemo02("\thttps://img-blog.csdnimg.cn/img_convert/20fb1225302c1c4070e929a6ab7f755e.png","jori3.png");
        //线程是同时执行的,每次执行的顺序不一定相同,由CPU安排调度的
        new Thread(r1).start();
        new Thread(r2).start();
        new Thread(r3).start();

    }

//下载器
class WebDownloader {
    //下载方法
    public void downloader(String url, String name) {

        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));//把url保存到文件
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("iO异常,downloader方法出现问题!");

        }
    }
}
}

练习 2:龟兔赛跑

public class RaceDemo implements Runnable {
    //胜利者只有一个,用static
    private static String winner;
    //重写run()
    @Override
    public void run() {
        for (int i = 0; i < 101; i++) {
            //当前线程是兔子的时候,每10步让他休息会,让乌龟线程先跑
            if (Thread.currentThread().getName().equals("兔子")&&i%10==0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            Boolean flag = gameOver(i);
            if (flag){//为true则比赛结束了,退出程序
                break;
            }
            System.out.println(Thread.currentThread().getName()+"-->>跑了"+i+"步");
        }
    }
    //判断是否完成比赛
    private boolean gameOver(int steps) {
        //判断胜利者是否存在

        if (winner != null) {//说明存在胜利者了
            return true;
        }
        {
            if (steps >= 100) {
                winner = Thread.currentThread().getName();//获取当前线程的名字
                System.out.println("winner is " + winner);
                return true;
            }
        }
        return false;
    }


    //main()主线程
    public static void main(String[] args) {
        RaceDemo raceDemo = new RaceDemo();
        new Thread(raceDemo,"兔子").start();
        new Thread(raceDemo,"乌龟").start();
    }
}

在这里插入图片描述

2.2.3 通过 Callable 和 Future 创建线程

实现Callable接口 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实现图片下载案例
/*callable的好处
* 1 可以定义返回值
* 2 可以主动抛出异常*/
public class CallableDemo implements Callable<Boolean> {
    private String url;//网络图片地址
    private String name;//保存的文件名
    //构造方法 初始化
    public CallableDemo(String url,String name){
        this.url = url;
        this.name = name;
    }
    //run方法是线程的执行体 ,重写run()方法
    @Override
    public Boolean call() {
        //创建下载器对象
        WebDownloader webDownloader = new WebDownloader();
        //调用下载方法
        webDownloader.downloader(url,name);
        System.out.println("下载了图片,文件名为:"+name);
        return true;
    }

    //主线程
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程对象
        CallableDemo t1 = new CallableDemo("\thttps://img-blog.csdnimg.cn/img_convert/4a295f7dd5255538958201ce0e637d11.png","jori1.png");
        CallableDemo t2 = new CallableDemo("\thttps://img-blog.csdnimg.cn/img_convert/88c9e642358704ad7a568169861af2ce.png","jori2.png");
        CallableDemo t3 = new CallableDemo("\thttps://img-blog.csdnimg.cn/img_convert/20fb1225302c1c4070e929a6ab7f755e.png","jori3.png");
        //线程是同时执行的,每次执行的顺序不一定相同,由CPU安排调度的

        /*开启线程步骤:
        * 1 创建执行服务 通过服务提交执行
        * 2 提交执行
        * 3 获取结果
        * 4 关闭服务*/
        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();
        //打印一下返回值
        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);
        ser.shutdown();//关闭服务
    }

//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){

        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));//把url保存到文件
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("iO异常,downloader方法出现问题!");

        }
    }
}
}

2.2.4 通过线程池创建线程

//线程池测试  
// 执行部分:可以用 execute 或者submit ,execute 放入的时Ruannable接口的实现类,没有返回值   submit放入的是Callable的实现类 有返回值
public class PoorDemo {
    public static void main(String[] args) {
//        1.创建服务,创建线程池 newFixedThreadPool(10);//池子大小
        ExecutorService service = Executors.newFixedThreadPool(10);//池子大小
//        2、执行Runnalbe接口的实现类 放入到excute
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

//        3、关闭服务
        service.shutdown();
    }

}

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

在这里插入图片描述

2.3 静态代理模式✅

静态代理模式是代理模式的其中一种,而代理模式又是23种设计模式之一
在这里插入图片描述

2.3.1 什么是静态代理模式?

对其他对象提供一种代理以控制对这个对象的访问。举个例子,如A对象有若干个方法,这时A对象对B对象进行委托授权,B对象便成了A对象的代理方,因此B对象便可对A对象进行访问并调用A对象的方法,相当于A对象调用自己的方法。现实生活中就行火车票代售点一样。

简单的说:代理模式,字面上的意思就是代理某个人去做某件事。比如代办证件,可以找代办公司帮忙代理办证。

2.3.2 静态代理模式案例

在这里插入图片描述

  • 将静态代理模式的三部分分别理解程:接口、客户端、委托类,并通过以下案例来演示

案例:婚庆公司代理YOU去结婚

  • 接口
//定义一个结婚的接口
interface Marry{
    void GetMarry();
}
  • 客户端
//真实角色 实现接口
class You implements Marry{
    //真实角色专注于做自己的事情
    @Override
    public void GetMarry() {
        System.out.println("you结婚了!很开心!");
    }
}
  • 委托类
//代理角色 婚庆公司 实现接口
class WeddingCommpany implements Marry{
    private Marry target;//对象类型的target,(new You() 匿名对象的传入)
    //构造方法
    public WeddingCommpany(Marry target) {
        this.target = target;
    }

    //重写方法  该方法封装了真实对象的request方法
    @Override
    public void GetMarry() {
        before();//添加了真实代理没有的方法
        target.GetMarry();//调用的是 代理类
        after();//添加了真实代理没有的方法
    }
    private void before() {
        System.out.println("结婚之前,筹备婚礼!");
    }
    private void after() {
        System.out.println("结婚之后,交付尾款!");
    }
}
  • 主函数
public class StaticProxyDemo {
    public static void main(String[] args) {
       
        WeddingCommpany weddingCommpany = new WeddingCommpany(new You());//匿名对象 传入到代理中
        weddingCommpany.GetMarry();//调用的是婚庆公司,但是结婚的是you
        /* 将上面两行代码简化成一行代码:new WeddingCommpany(new You()).GetMarry()*/
    }
  • 类比一个类(假设是RunnableDemo)继承Runnable接口并用Thread类代理

//实现多线程,实现Runnable接口 外面的代理给里面的真实对象做事,代理和真实对象都实现了同一个接口 如: Thread 和new RunnableDemo()都实现了Runnable接口 /* new Thread(new Runnable() { @Override public void run() { System.out.println("i love you!"); }*/ //lambda表达式 jdk8 新特性 new Thread(()->System.out.println("i love you")).start() ;

效果:
在这里插入图片描述

静态代理模式总结:

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真实对象
    好处:
  • 代理对象可以做很多真实对象做不到的事情
  • 真实对象专注做自己的事情

缺点:静态代理对于代理的角色是固定的,如dao层有20个dao类,如果要对方法的访问权限进行代理,此时需要创建20个静态代理角色,引起类爆炸,无法满足生产上的需要,于是就催生了动态代理的思想。(之后再总结出来)