• 多线程编程


    多线程

    进程

    在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程。

    某些进程内部还需要同时执行多个子任务。例如,我们在使用Word时,Word可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行打印,我们把子任务称为线程

    每一个进程都是有程序代码组成的,这些代码在进程中 有CPU轮流进行执行

    操作系统调度的最小任务单位其实不是进程,而是线程。

    因为同一个应用程序,既可以有多个进程,也可以有多个线程

    同一个应用程序,既可以有多个进程也可以有多个线程

    • 多进程模式(每个进程有一个线程)
    • 多线程模式(一个进程有多个线程)
    • 多进程+多线程(复杂度最高)

    线程

    线程是进程中的一个执行单元/执行路径,负责当前进程中程序的执行

    一个进程中至少要有线程,也可存在多个线程的,这种程序我们称之为多线程程序

    进程和线程是包含关系,但是多任务既可以由多进程实现,也可以由单进程内的多线程实现,还可以混合多进程+多线程。

    具体采用哪种方式,要考虑到进程和线程的特点。

    和多线程相比,多进程的缺点在于:

    • 创建进程比创建线程开销大,尤其是在Windows系统上;
    • 进程间通信比线程间通信要慢,因为线程间通信就是读写同一个变量,速度很快。

    而多进程的优点在于:

    多进程稳定性比多线程高,因为在多进程的情况下,一个进程崩溃不会影响其他进程,而在多线程的情况下,任何一个线程崩溃会直接导致整个进程崩溃。

    和单线程相比,多线程编程的特点在于:多线程经常需要读写共享数据,并且需要同步。

    多线程编程的复杂度高,调试更困难。

    创建多线程

    实现多线程有两种方式 继承Thread类和实现Runnable接口

    要创建一个新线程,我们需要实例化一个Thread实例,然后调用它的start()方法

    继承Thread

    Thread派生一个自定义类,然后覆写run()方法

    一个类继承了Thread类的话 就具备了可以被多线程执行的功能

    继承Thread 相当于与获得了一个 执行路径
    还差一个 执行任务 重写run函数
    run函数不是由我们调用的 由JVM调用
    start开辟一个执行路径 由虚拟机具体执行 run

    具体步骤:

    ​ (1)定义一个类继承Thread
    ​ (2)重写run函数
    ​ (3)创建子类对象,就是创建线程对象
    ​ (4)调用start方法,开启线程并让线程执行,同时告诉JVM去调用run方法

    public class Main {
        public static void main(String[] args) {
            Demo d1 = new Demo("小红");
            Demo d2 = new Demo("小明");
            System.out.println(d1.getName());   //Thread-0 小红
            System.out.println(d2.getName());   //Thread-1 小明
    
            d1.start();
            d2.start();
            for (int i = 1; i <= 50; i++) {
                System.out.println(Thread.currentThread().getName() + i);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //return
        }
    }
    
    class Demo extends Thread {
        public Demo(String name) {
            super(name);    //-> Thread() -> "Thread-" + num
        }
        public void show() {
            for (int i = 1; i <= 50; i++) {
                System.out.println(getName());  //当前线程对象的名称
                //当前代码所在线程对象的名称
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
        //run函数描述的就是当前线程需要被执行的任务
        @Override
        public void run() {
            for (int i = 1; i <= 50; i++) {
                System.out.println(Thread.currentThread().getName() + i);
                try {
                    sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                //一个线程运行的过程中个如果出现异常 则不会影响其他线程的执行!
                
                if (getName().equals("小明") && i == 30) {
                    throw new NullPointerException();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    start()方法会在内部自动调用实例的run()方法。

    实现Runnable接口

    创建Thread实例时,传入一个Runnable实例

    如果某一个类已经继承了别的类 则不能在重新继承Thread

    (1)定义类实现Runnable接口 创建一个任务类
    (2)重写run函数 就是任务的具体执行内容
    (3)外面创建Thread对象 创建执行路径
    (4)任务对象与Thread对象进行关联:将Runnable接口的子类对象作为参数传递给Thread的构造函数
    (5)调用start()

    public class Main {
        public static void main(String[] args) {
            PrintNumberTask r1 = new PrintNumberTask();
            Thread t1 = new Thread(r1);
    
            PrintNumberTask r2 = new PrintNumberTask();
            Thread t2 = new Thread(r2);
    
            Thread t3 = new Thread(()->{
                for (int i = 1; i <= 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "-" + i);
                }
            });
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    class PrintNumberTask implements Runnable {
        @Override
        public void run() {
            for (int i = 1; i <= 20; i++) {
                System.out.println(Thread.currentThread().getName() + "-" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    用Java8引入的lambda语法进一步简写为:

    public class Main {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable());
            t.start(); // 启动新线程
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("start new thread!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    要模拟并发执行的效果,我们可以在线程中调用Thread.sleep(),强迫当前线程暂停一段时间

    用多线程的技术去复制目录中指定文件

    import java.io.*;
    
    //用多线程的技术去复制目录中指定文件
    public class Main {
        public static void main(String[] args) {
            //创建文件对象
            File source = new File("C:\\Users\\jameth\\Desktop\\aab\\JavaSE");
            File aim = new File("C:\\Users\\jameth\\Desktop\\Code");
            //判断路径是否存在
            if (!aim.exists()) {
                aim.mkdir();
            }
            //拷贝文件
            traversal(source, aim);
        }
    
        private static void traversal(File dir, File aimdir) {
            File[] files = dir.listFiles();
            if (files != null && files.length != 0) {
                for (File file : files) {
                    if (file.isFile()) {
                        if (file.getName().endsWith(".java")) {
                            //创建执行任务对象
                            CopyFileTask task = new CopyFileTask(file, new File(aimdir, file.getName()));
                            //创建执行道路 并且关联执行任务
                            Thread thread = new Thread(task);
                            thread.start();
    
                        }
                    } else {
                        traversal(file, aimdir);
                    }
                }
            }
        }
    }
    
    class CopyFileTask implements Runnable {
        private File source;
        private File aim;
    
        public CopyFileTask(File source, File aim) {
            this.source = source;
            this.aim = aim;
        }
    
        @Override
        public void run() {
            BufferedInputStream bis = null;
            FileInputStream fis = null;
            BufferedOutputStream bos = null;
            FileOutputStream fos = null;
            try {
                fis = new FileInputStream(source);
                fos = new FileOutputStream(aim);
                bis = new BufferedInputStream(fis);
                bos = new BufferedOutputStream(fos);
                byte[] buf = new byte[1024 * 1024];
                int len = 0;
                while ((len = bis.read(buf)) != -1) {
                    bos.write(buf, 0, len);
                    bos.flush();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (bis != null) {
                    try {
                        bis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (bos != null) {
                    try {
                        bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println(source.getName() + "拷贝完成!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    Java用Thread对象表示一个线程,通过调用start()启动一个新线程;

    一个线程对象只能调用一次start()方法;

    线程的执行代码写在run()方法中;

    线程调度由操作系统决定,程序本身无法决定调度顺序;

    Thread.sleep()可以把当前线程暂停一段时间。

  • 相关阅读:
    手写编程语言-如何为 GScript 编写标准库
    阿里的三个「价值支点」
    一篇文章让你理解 大数据所需要的组件
    Linux--I/O复用之select
    msvcp140.dll丢失怎样修复?常用的5个修复方法总结
    docker运行centos镜像 安装python3.9环境
    SpringBoot结合Vue.js+axios框架实现增删改查功能+网页端实时显示数据库数据(包括删除多条数据)
    逆向学习记录(4)adb
    云龙开炮版飞机大战(完整版)
    网络通信(套接字通信)(C/C++)
  • 原文地址:https://blog.csdn.net/m0_59138290/article/details/128007876