欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

week04_day03_Thread01

程序员文章站 2022-05-09 14:06:31
...

引入多线程:
假如我要实现如下功能

程序不停地在屏幕上输出一句问候的语句(比如“你好”)
同时,当我通过键盘输入固定响应的时候,程序停止向屏幕输出问候的语句

好像很简单,那我们就来实现一下吧。

第一段代码用的单线程。
突然发现,看似很简单的功能,用我们之前的技术,完成不了

分析一下,这里为啥我们完成不了?
核心原因在于两个简单的功能似乎在“同时”执行

如何实现上述功能呢?多线程

多线程究竟做了什么?
从效果上来看,似乎使得同一程序中的不同部分的代码,“同时”运行起来了。

public class SingleThread {

    static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        sayHelloRecycling();
        //后面的代码都不会执行,程序卡在sayHelloRecycling()的while(true)中
        System.out.println("after sayHelloRecycling");
        waitToStop();
        System.out.println("main end");
    }

    private static void waitToStop() {
        //实现,通过键盘输入固定字符串比如:gun
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String input = scanner.nextLine();
            if ("gun".equals(input)) {
                flag = false;
                break;
            }
        }

    }

    private static void sayHelloRecycling() throws InterruptedException {
        // 每隔2秒种,通过循环实现,不停的向屏幕输出哈哈, 你好!
        while (flag) {
            System.out.println("哈哈, 你好!");
            //等待三秒钟
            Thread.sleep(3000);
        }
    }
}

第二段代码用的多线程。

 /*
 * 一个线程,就是一条独立的执行路径
 */
public class MultiThread {

    static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        sayHelloRecycling();
        waitToStop();
        System.out.println("main end");
    }

    /**
     *  在子线程中,接收键盘输入,并根据键盘输入
     *  ,决定是否终止,在屏幕上输出问候语句
     */
    private static void waitToStop() {
        new Thread() {
            @Override
            public void run() {
                Scanner scanner = new Scanner(System.in);
                while (true) {
                    String s = scanner.nextLine();
                    if ("gun".equals(s)) {
                        flag = false;
                        scanner.close();
                        break; //如果输入了gun,就终止程序的运行,我上课是出现的
                    }
                }

            }
        }.start();
    }

    /**
     *  在子线程中,不停的在屏幕上输出问候语句,
     *  直到,循环的控制变量flag的值被改为false
     */
    private static void sayHelloRecycling() throws InterruptedException {
        new Thread() {
            @Override
            public void run() {
                while (flag) {
                    System.out.println("哈哈, 你好!");

                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

单线程与多线程执行路径对比:
week04_day03_Thread01
·················································································································································································································

前提:计算机中,CPU就是专门用来做运算,一切运算都是由CPU,而且CPU它是我们计算机中最宝贵的资源
其实,我们是把CPU的用来计算时间,当作是一种资源

只有一个CPU,单核

为什么会有多进程?

  1. 单道批处理:
    单道:在整个操作系统中,同一时间,内存中只有 一个程序 在运行,程序的运行,只能是上一个程序运行完,下一个程序开始运行
    批处理:在程序运行过程中,不会有任何响应,直到程序运行结束。

    单道批处理操作系统,它并不能很好的利用CPU的计算时间
    比如:假设,在单道批处理系统中,运行了一个程序,假设在它的程序中,它需要执行IO(和打印机传输数据),IO的数据传输过程中
    有很大一部分时间,是不会用到CPU的计算功能,此时CPU闲置。

  2. 多道批处理操作系统? 就是为了提高CPU的利用率
    多道:在操作系统中,同时内存中,可以有多个应用程序,在运行,这样一来,一旦某个应用程序,不需要使用cpu的计算功能,我们,操作系统就会把cpu计算时间,分配给内存中的其他应用程序来计算。这样一来,cpu就大大提高了。
    应用程序的执行:当应用程序,占用cpu执行时间,这个应用才算真正的执行
    在多道批处理系统中,多个应用程序,交替执行,看起来好像多个应用程序在“同时”运行

    核心原因:进程的交替执行,交替过程,是需要付出额外代价的 —— 进程的上下文切换

  3. 现代操作系统,引入了另外一个东西,线程
    线程,又被称之为轻量级进程,一个进程中可以有多个线程
    同一个进程中的多个线程,线程上下文切换的时候,付出额外代价,小的多

    并发: 并发执行,一段时间内,多个程序,同时运行
    并行(多CPU): 并行执行,同一个时间点,多个程序同时运行

    通常我们生活中所说的同时,指的是并行

一个进程中有多个线程,进行线程切换时,只需要切换一小部分资源即可。
如果没有线程,进程的上下文切换时我们需要保存一个进程的很多东西,同时又需要恢复另一个线程的很多很多东西。这样就十分耗费CPU资源,耗费时间。

week04_day03_Thread01

·················································································································································································································
每运行一个java程序都会创建一个新的进程

Java命令运行一个Java程序的过程?
JVM是单线程还是多线程?

package com.cskaoyan.thread.basic;

/**
 * @author shihao
 * @create 2020-04-29 16:51
 *
 *
 *  1. Java命令运行一个Java程序的过程?  java 字节码文件名
a. 其实java命令,它启动了一个jvm进程
b. 该jvm进程,在执行的时候,首先会创建一个线程,main线程
c. 在main线程中,运行主类中的main方法代码

2. JVM是单线程还是多线程? Jvm天生多线程(用垃圾回收器的执行,证明)

 */
public class Demo01 {

    public static void main(String[] args) {

        while (true) {
            //占用堆空间,占用完之后就变成了垃圾。
            int[] array = new int[8192];
            array = null;

            //其实,在jvm,还有另外一个线程, 在执行垃圾回收器的垃圾回收代码
            //所以jvm天生多线程。只不过我们直观感受到的是main线程
            //你不停的产生垃圾,垃圾回收器也在不停的回收垃圾。

        }

    }

}


·················································································································································································································

Thread是具体类,不是抽象类

Thread实现方式一:
1.继承Thread
2.重写子类的run方法
3.创建该子类的对象
4.启动线程 start()

注意事项:

  1. 一个Thread类(Thread子类) 对象 代表一个线程

  2. 为什么我们重写Thread类中的run方法
    ——> 只有Thread run()方法中的代码,才会执行在子线程中
    为了保证,子线程中运行的是我们想要在子线程中运行的代码

  3. 但是,如果想要让代码,在子线程中运行,并非一定,代码要写在run方法方法体中。对于,定义在该Thread子类中,其他方法方法体中的代码(如test()),也可以运行在子线程。
    换句话说,一个方法,被哪个线程中的代码调用,被调用的方法,就运行在,调用它的线程中。(test()方法被run()方法调用就运行在MyFirstThread对象中,如果别main方法调用就运行在main线程中)

  4. 启动线程,必须使用start()方法来启动,这样才能使Thread中的run方法运行在子线程中
    如果, 如果通过调用run方法,来执行Thread的run方法代码,这仅仅只是普通的方法调用

  5. 同一个Thread或Thread子类对象(代表同一个线程),只能被启动一次
    如果,我们要启动多个线程,只能创建多个线程对象,并启动这些线程对象
    这和线程的状态有关系,一个线程只能从一个新建状态转化为死亡状态,死亡后就变成垃圾

public class Demo02 {

    public static void main(String[] args) {

        //3.创建该子类的对象
        MyFirstThread thread = new MyFirstThread();

        //启动线程 start(), 是通过start方法启动线程
        //thread.start();


        // 测试启动线程, 这种方式,并不能启动线程
        //thread.run();

        //System.out.println("main end");

        // 多次启动线程, 同一线程启动两次(这是错误的方式,同一个线程只能被启动一次)
        //thread.start();
        //thread.start();

        // 如果要启动多个线程,那就创建多个线程对象并启动
        MyFirstThread thread1 = new MyFirstThread();
        MyFirstThread thread2 = new MyFirstThread();

        thread1.start();
        thread2.start();


    }
}

//1.继承Thread
class MyFirstThread extends Thread {

    // 2.重写子类的run方法
    @Override
    public void run() {

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 只有Thread类的run方法中的代码,才会执行在子线程中
        System.out.println("hello, thread");

        //在run方法中被调用,该方法就运行在子线程中
        //test();
    }


    public void test() {

    }
}

·················································································································································································································

作业:复制目录,要求:
a. 复制目录及其所有子目录,保证在复制的目标目录中,目录结构和原目录相同
b. 同时,对于原目录及其子目录,只将原目录中的指定类型的.java文件,复制到目标目录中,对应的相同目录中
week04_day03_Thread01

package com.cskaoyan.thread;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
 * @author shihao
 * @create 2020-04-29 21:13
 */
public class test {

    public static void main(String[] args) throws IOException {

        //比如,要在e盘中复制d盘的dir目录
        multiCopy(new File("F:\\dir"), new File("F:\\test\\dir"));
    }

    /**
     * @param src   当前待复制的目录
     * @param dest   在dest目录下,复制src目录
     * @throws IOException
     */
    public static void multiCopy(File src, File dest) throws IOException {

        //如果src是文件
        File[] files = src.listFiles();
        if (files == null) {
            return;
        }

        //获取待复制文件目录的名字
        String name = src.getName(); //dir
        // targetDir就是对src目录的复制
        File targetDir = new File(dest, name); //创建了和src平级的父目录  dir
        // targetDir复制该目录本身,它就是对src目录的复制目录,但该目录内容为空
        targetDir.mkdirs();

  /*
       遍历src目录的子文件或子目录,并在复制的目标目录targetDir中,复制src中的子文件或子目录
   */
        for (int i = 0; i < files.length; i++) {
            if (files[i].isDirectory()) {
                //如果是目录,就递归复制当前目录
                multiCopy(files[i], targetDir);
            } else {
                //如果是文件
                if (files[i].getName().endsWith(".txt")) {
                    // 而且是java文件
                    File targetFile = new File(targetDir, files[i].getName());
                    // 则在targetDir目录中,复制src目录中的java文件
                    copyFile(files[i], targetFile);
                }
            }
        }
    }

    /*
         将源文件src,复制到目标文件dest中
     */
    public static void copyFile(File src, File dest) throws IOException {
        FileReader fr = new FileReader(src);
        FileWriter fw = new FileWriter(dest);
        int len;
        char[] buffer = new char[1024];
        while ((len = fr.read(buffer)) != -1) {
            fw.write(buffer, 0, len);
        }
        fr.close();
        fw.close();
    }
}

相关标签: JavaSE