线程简介

  1. 多任务
  2. 多进程
  3. 多线程

多任务任务

eg.司机边打电话边开车边打点滴、吃饭的时候我们边玩手机边吃饭、边上厕所边玩手机。

本质:看起来是多个任务在做,其实大脑在同一时间依旧只做了一件事。

多进程

eg.原来是一条路,结果通的车多了,道路堵塞,效率极低。为了提高效率,充分利用道路,于是乎多加了一条道路

duoxianchen

一个进程可以有多个线程,如视频中听到声音,图像,弹幕,等等。

多线程

  • 通常在一个进程中可以包括若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义
  • 线程是CPU调度和执行的单位。
  • 并行与并发:
    1. 并行:指的是两个或多个事件在同一时刻发生(同时发生)。
    2. 并发:指两个或多个事件在同一时间段内发生。

进程与线程

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建运行到消亡的过程。
  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

jie

注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如 
服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一地点, 
CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

创建线程

  • 方法一:

    继承Thread类

    //创建线程方法一:继承Thread类,重写run()方法,调用star()开启线程
    public class Theard01 extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("我是线程");
            }
        }
    
        public static void main(String[] args) {
            //创建一个线程对象
            Theard01 theard01 = new Theard01();
            //start()方法开启线程
            theard01.start();
            for (int i = 0; i < 300; i++) {
                System.out.println("我是主线程");
            }
        }
    }
    
运行图:

注意:运行的结果是随机的,每次运行的情况可能会不同,这取决于你电脑的CPU运行速度。线程开启不一定执行,由CPU调度执 行。

  • 方法二:

    Runnable接口

    public class Thread03 implements Runnable{
        //线程
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("我是线程"+i);
            }
        }
    
        public static void main(String[] args) {
            //创建Runnable接口的实现类对象
            Thread03 thread03 = new Thread03();
    
            //创建创建线程对象来开启线程
            new Thread(thread03).start();
            for (int i = 0; i < 300; i++) {
                System.out.println("我是主线程"+i);
            }
        }
    }
    
运行图

我们可以看到使用Runnable接口使用的方法,其线程互相运行更加频繁。

静态代理

假设一个场景,你要结婚而你却有非常多的事完成,忙的不可开交。这时候你就会想到请婚庆公司帮忙,帮助你完成婚礼布置的现场,而你就只需要的等着结婚的那天和新娘一块去现场结婚,不需要你自己布置就能完成待客,以及现场的布置。婚庆公司也就是相当于现场中的静态代理。

//静态代理
public class Thread04 {
    public static void main(String[] args) {
        //代理结婚
        You you = new You(); //获取真实角色
        Agency agency = new Agency(you); //获取代理角色,并将真实角色放入代理角色中
        agency.MarryDay(); 	//婚庆公司只帮你布置细节,最终的任务还是你自己亲自结婚
        //代理结婚缩写
        new Agency(new You()).MarryDay();
        //对比多线程启动
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("结婚");
            }
        }).start();
        //多线程缩写
        new Thread(()-> System.out.println("结婚")).start();
    }
}

//目的:结婚的一天
interface Marry{
    void MarryDay();
}

//真实本人
class You implements Marry{
    @Override
    public void MarryDay() {
        System.out.println("你本人结婚");
    }
}

//代理真人
class Agency implements Marry{
    private Marry people;

    public Agency(Marry people) {
        this.people = people;
    }

    @Override
    public void MarryDay() {
        befor();
        this.people.MarryDay();
        arter();
        agencypeople();
    }

    private void agencypeople() {
        System.out.println("委托结束");
    }

    private void arter() {
        System.out.println("收彩礼");
    }

    private void befor() {
        System.out.println("准备婚服");
    }
}
比较于多线程

Agency()相当于Thread(),Marry()相当于start()。

new You() 相当于现场里的run()方法。

new Agency(new You()).MarryDay();
new Thread(()-> System.out.println("结婚")).start();

对比发现在多线程的这种实现方法中就用到了静态代理模式,其实Thread类就相当于是代理(例子里里面的婚庆公司),它代理TestNewThread02类去实现start()方法。其实Thread类也实现了Runnable,TestNewThread02类也实现了Runnnable,点开Thread源码,

我们可以发现Thread也是Runnable的一个接口,那么也就能明白Thread也就是等同于Agency这个婚庆公司去代理我们的细节。(对比线程创建的第二种方法。)

线程练习

  • 练习Thread,实现多线程同步下载图片

    import org.apache.commons.io.FileUtils;
    
    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    
    public class Theard02 extends Thread{
        private String url;//网络图片地址
        private String name;//网络图片名字
    
        public Theard02(String url,String name){
            this.url = url;
            this.name = name;
        }
        //下载图片的执行体
        @Override
        public void run() {
            WebDownload webDownload = new WebDownload();
            webDownload.download(url,name);
            System.out.println("您下载了名为" + name + "的文件");
        }
    
        public static void main(String[] args) {
            Theard02 theard01 = new Theard02("https://www.jauxgit.top/upload/2021/02/Linux-fa1f472e84fd4604a0460781a4fbaf12.png","JAUX.png");
            Theard02 theard02 = new Theard02("https://www.jauxgit.top/upload/2021/02/logo-04f5d0a71ed04cb7acc11b1fc428d0e6.png","bk.png");
            Theard02 theard03 = new Theard02("https://www.jauxgit.top/upload/2021/02/study-e2c9aae9aeb54cc9829d0ca1395d8481.jpg","study.jpg");
    
            //惯性思维,我们想是依次01,02,03下载
            theard01.start();
            theard02.start();
            theard03.start();
        }
    }
    
    //下载器
    class WebDownload{
        //下载方法
        public void download(String url,String name){
            try {
                FileUtils.copyURLToFile(new URL(url),new File(name));
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("IO异常,download方法出现问题");
            }
        }
    }
    
    运行图

    运行

    我们惯性思维肯定是依次下载JAUX,bk,study。

    然而真实的结果却是依次study,JAUX,bk,说明在多线程运行下三个下载线程为随机争取下载速度,而不是依次下载,这也是多线程的魅力。

比较Thread继承&Runnable接口

  • 继承Thread类
    • 子类继承Thread类具备多线程能力。
    • 启动线程:子类对象.star()。
    • 不建议使用:避免opp单继承局限性。
  • 实现Runnable接口
    • 实现接口Runnable具有多线程的能力。
    • 启动线程:传入目标对象+Thread对象.star()。
    • 推荐使用:避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用。

案例

龟兔赛跑

规则:
  1. 赛道距离为100米,到达终点为胜利。
  2. 判断比赛是否结束。
  3. 比赛开始,打印胜利者。
  4. 无论如何跑,乌龟始终获得胜利。
  5. 兔子必须碎觉。
public class Race implements Runnable{
    private static String winner;

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            //模拟兔子碎觉
            if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0){
                try {
                    Thread.sleep(1);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            //判断比赛是否结束
            boolean flag = gameOver(i);
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步");
        }
    }

    //判断是否完成比赛
    private boolean gameOver(int steps){
        //判断是否有胜利者
        if (winner != null){//已经存在胜利者
            return true;
        }else if (steps >= 100){
            winner = Thread.currentThread().getName();
            System.out.println("winner is "+winner);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Race sport = new Race();
        new Thread(sport,"乌龟").start();
        new Thread(sport,"兔子").start();
    }
}
结果

相关视频(狂神)