多线程复习详解

最近在准备面试,准备把以前的东西复习一下和学习一下新东西。写博客做个记录,会分为几个大类吧

多线程概述

普通方法和多线程的区别

  1. 普通方法走的是run(),执行完run之后才继续
  2. 多线程是start(),多条执行路径,主线程和子线程交替进行

Process(过程,即进程)与Thread(线程)的总结

  1. 说起进程,就要提到程序,程序是指令和数据的有序集合,本身没有任何含义,是一个静态的

  2. 进程就是执行一次程序的过程,是动态的,是系统分配资源的单位

  3. 一个进程中可以有多个线程(如一部电影是画面,声音,弹幕等组成的)且至少有一个线程,线程是CPU调节和调度的单位

    PS:代码中很多线程是模拟出来的,真正的多现实应该是需要多个CPU的,但只有一个CPU的时候,一个时间点CPU只能执行一个代码,不过因为CPU切换的很快,造成了多线程的错觉

  4. 线程就是独立执行的路径,程序运行时,即使没有自己创建的线程,后台也会有多个线程(main主线程,gc垃圾处理线程等).

  5. main()是主线程,是系统的入口,用于执行整个程序,在程序执行过程中,如果有多个线程,线程的调度运行是由调度器安排调度的,调度器和操作系统密切相关,先后顺序不能人为干预。

  6. 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。

  7. 线程多不一定是有点,因为多线程会带来额外开销,如CPU调度时间,并发控制开销。

  8. 很重要的一点,每个线程只在自己的工作内存交互,内存控制不当会造成数据不一致。

多线程详解(Thread,Runnable,Callable)

Thread class

操作过程

  1. 因为是个类,所以是要继承(只能继承一个),所以我们需要自定义线程类继承

  2. 重写run()方法,编写线程执行体,即你想跑的线程内容

  3. 创建线程对象,调用start()方法启动线程1,先new一个方法,如Thread1 Th1 = new Thread1()。2,调用start方法,Th1 .start()

代码

public class Thread1 extends Thread {
    @Override
    public void run() {
        //super.run();
        //这里我们就是写run方法线程体,即我们想写的线程
        for (int i = 0; i < 200; i++) {
            System.out.println("这是thread1的第"+i+"次执行");
        }
    }

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

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



        for (int i = 0; i < 200; i++) {
            System.out.println("这是主线程的第"+i+"次执行");
        }

    }

下面的是结果图示例,多线程开启之后,会随机执行某个线程

Thread总结

  1. 这个方法是先重写run方法,再调动start方法
  2. 线程开启不一定立即执行,是由CPU调动的

Tread练习(下载网络图片)

package com.xiaoke.study.day01.com.xiaoke.study.Demo02_Thread;

import org.apache.commons.io.FileUtils;

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

/**
 * @author :xiaole
 * @version :1.0
 * 内容:多线程下载网图
 * */
public class Thread2 extends Thread{

    //创建url和文件名称
    private String url;//图片地址
    private String fileName;//文件名

    public Thread2(String url,String fileName){
        this.url = url;
        this.fileName = fileName;
    }

    @Override
    public void run() {
        WebDownload webDownload = new WebDownload();

        webDownload.download(url,fileName);
        System.out.println("下载的文件地址是"+ url);
        System.out.println("下载的文件地址是"+ fileName);

    }

    public static void main(String[] args) {


        //https://pic.cnblogs.com/face/2273763/20211109003826.png
        //上面的文件地址
        Thread2 test01 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke01.jpg");
        test01.start();
        Thread2 test02 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke02.jpg");
        test02.start();
        Thread2 test03 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke03.jpg");
        test03.start();
        
        //最后下载后的图片在当前文件的同一层
    }
}


//下载的类
class WebDownload{

    //下载方法
    public void download(String url,String fileName){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(fileName));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("捕获到了download方法的IO异常");
        }

    }


}

下面的是图片位置

Runnable接口

操作过程

  1. 因为是个接口,所以实现接口就可以(implements Runnable)
  2. 重写run()方法,编写线程执行体,即你想跑的线程内容
  3. 创建线程对象,调用start()方法启动线程1,先new一个方法,如Thread1 Th1 = new Thread1()
  4. 通过线程对象开启线程

代码

package com.xiaoke.study.day01.com.xiaoke.study.Demo02_Thread;

public class Thread3 implements Runnable {

    @Override
    public void run() {
        //super.run();
        //这里我们就是写run方法线程体,即我们想写的线程
        for (int i = 0; i < 200; i++) {
            System.out.println("这是Runnable1的第"+i+"次执行");
        }
    }

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

        //创建Runnable接口的实现类对象(runnable只是一个接口,还是需要线程的相关类实现功能)
        Thread3 th3 = new Thread3();

//        //1。创建线程对象,通过线程对象开启线程,代理
//        Thread thread = new Thread(Th3);
//        //2.调用start方法开启线程
//        thread.start();

        //以上两步骤可以合并成一步
        new Thread(th3).start();


        for (int i = 0; i < 200; i++) {
            System.out.println("这是主线程的第"+i+"次执行");
        }

    }

}

Runnable总结

  1. 原理本质和Thread是一样的
  2. 先要有接口
  3. 要创建Runnable接口的实现类对象( Thread3 th3 = new Thread3() )
  4. 创建线程对象,通过线程对象开启线程,代理,最后还是通过star方法

Runnable练习(下载网络图片,只更改了一个方法)

package com.xiaoke.study.day01.com.xiaoke.study.Demo02_Thread;

import org.apache.commons.io.FileUtils;

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




//如果是通过接口,则是用以下方法
public class Thread2 implements Runnable{

    //创建url和文件名称
    private String url;//图片地址
    private String fileName;//文件名

    public Thread2(String url,String fileName){
        this.url = url;
        this.fileName = fileName;
    }

    @Override
    public void run() {
        WebDownload webDownload = new WebDownload();

        webDownload.download(url,fileName);
        System.out.println("下载的文件地址是"+ url);
        System.out.println("下载的文件地址是"+ fileName);

    }

    public static void main(String[] args) {


        //https://pic.cnblogs.com/face/2273763/20211109003826.png
        Thread2 test01 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke01.jpg");
        new Thread(test01).start();
        Thread2 test02 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke02.jpg");
        new Thread(test02).start();
        Thread2 test03 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke03.jpg");
        new Thread(test03).start();
    }
}





//下载的类
class WebDownload{

    //下载方法
    public void download(String url,String fileName){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(fileName));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("捕获到了download方法的IO异常");
        }

    }


}

Thread类和Runnable的对比

继承Thread类

  1. 子类继承Thread类具备多线程能力

  2. 启动线程:子类对象,start()

    *不建议使用,避免OOP单线程局限性

实现Runnable接口

  1. 实现Runnable接口具有多线程能力

  2. 启动线程:传入目标对象+Thread对象.start()

    *推荐使用,避免单继承局限性,灵活方便,且同一个对象可以被多个线程使用

Callable接口

多线程代码和问题总结

多线程并发问题,即处理同一个资源的数据问题(前面有提到)

  1. 举例买火车票,代码和结果如下

    package com.xiaoke.study.day01.com.xiaoke.study.Demo02_Thread;
    
    //模拟多人买火车票
    //即通过开启多线程模拟不同的人
    
    //问题总结,多个线程操作同一个线程的时候,线程不安全。数据紊乱,下面几行就是某次的结果
    //        恭喜xiaoke抢到了第3张票
    //        恭喜xiaosu抢到了第4张票
    //        恭喜xiaoka抢到了第4张票
    //        恭喜xiaoka抢到了第2张票
    //        恭喜xiaosu抢到了第1张票
    public class Thread4 implements Runnable{
    
        //设置票数
        private int ticketNumber = 200;
    
        @Override
        public void run() {
            while (true){
                if (ticketNumber <= 0){
                    System.out.println("票已售罄");
                    break;
                }
                //模拟延时  Thread.sleep
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("恭喜"+Thread.currentThread().getName()+"抢到了第"+ticketNumber--+"张票");
            }
        }
    
    
        public static void main(String[] args) {
    
            Thread4 thread4 = new Thread4();
    
            new Thread(thread4,"xiaoke").start();
            new Thread(thread4,"xiaoka").start();
            new Thread(thread4,"xiaosu").start();
    
        }
    
    }
    
    
    

    结果如下图

  2. 写个龟兔赛跑(简单避免一下上图中占用统一资源的问题)

    package com.xiaoke.study.day01.com.xiaoke.study.Demo02_Thread;
    
    public class Thread5_saipao implements Runnable {
    
        //使用static,这样任何时候就可以只判断一次,在成功的时候赋值就可
    
        private static boolean flag  = false;
    
        //写赛事和对赛事结果的判断
        @Override
        public void run() {
    
    
            for (int i = 0; i <= 100; i++) {
                getWinner(i);
                //因为不止一个人,所以这边要先判断是否已经走完了
                //即兔子和乌龟哪个赢了
                if (flag){
                    break;
                }
                if (Thread.currentThread().getName()=="兔子"){
                    if(i!=0 && i/10 == 0)
                    {
                        try {
                            Thread.sleep(20);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                System.out.println(Thread.currentThread().getName()+"已经走到了第"+i+"步");
            }
        }
    
        public void getWinner(int i){
            if(flag != true)
            {
                if(i==100){
                    flag =  true;
                    System.out.println("这场比赛的胜利者是"+Thread.currentThread().getName());
                }
            }
        }
    
    
        public static void main(String[] args) {
            Thread5_saipao thread5_saipao = new Thread5_saipao();
    
            new Thread(thread5_saipao,"兔子").start();
            new Thread(thread5_saipao,"乌龟").start();
    
        }
    
    }
    
    

总体总结

  1. 线程开启不一定立即执行,由CPU调度执行
  2. start才是开启线程的方法,run只是单纯的执行方法,不会开启多线程
posted @ 2022-03-17 01:48  啃兔子的大萝卜  阅读(39)  评论(0)    收藏  举报