多线程基础

多线程基础


交替任务

现代操作系统(如Windows、macOS、Linux)都可以执行多任务。多任务就是指系统同时运行多个任务,例如:在系统上同时打开浏览器、音乐播放器、视频播放器等就是多任务。

cpu执行代码是一条一条顺序执行的,事实上,即使是单核cpu,也可以同时运行多个任务,因为操作系统执行多任务实际上就是让cpu对多个任务轮流交替执行。

举个栗子:假如我们有语文、数学、英语三门作业要完成,每个作业需要30分钟的时间。把这3门作业看作3个任务,我们可以先做1分钟语文,再做1分钟数学,再做1分钟英语。这样轮流做下去,在现实世界中看起来也许速度很慢,但是把这样的模式迁移到计算机世界,在cpu的强大处理能力之下,做作业的速度就会看起来很快,就像是同时在做3门作业。

类似的,操作系统轮流让多个任务交替执行,举个栗子:让浏览器执行0.001秒,让qq执行0.001秒,再让音乐播放器执行0.001秒,在人看起来,cpu就像是在同时执行多个任务。

由于任务数往往是多于cpu核数的,所以即使是多核cpu也是任务交替执行的。

进程

在计算机中,一个任务就是一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程。在系统的任务管理器中,我们可以看到当前系统正在进行的进程有哪些。

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

进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程

操作系统调度的最小任务单位其实不是进程,而是线程。常用的Windows、Linux等操作系统都采用抢占式多任务,如何调度进程完成有操作系统决定,程序自己不能决定什么时候执行以及执行多长时间。

因为同一个应用程序,既可以有多个进程,也可以有多个线程,因此,实现多任务的方法,有以下几种:

多进程模式(每个进程只有一个线程):

┌──────────┐ ┌──────────┐ ┌──────────┐
│Process   │ │Process   │ │Process   │
│┌────────┐│ │┌────────┐│ │┌────────┐│
││ Thread ││ ││ Thread ││ ││ Thread ││
│└────────┘│ │└────────┘│ │└────────┘│
└──────────┘ └──────────┘ └──────────┘

多线程模式(一个进程有多个线程):

┌────────────────────┐
│Process             │
│┌────────┐┌────────┐│
││ Thread ││ Thread ││
│└────────┘└────────┘│
│┌────────┐┌────────┐│
││ Thread ││ Thread ││
│└────────┘└────────┘│
└────────────────────┘

多进程+多线程模式(复杂度最高):

┌──────────┐┌──────────┐┌──────────┐
│Process   ││Process   ││Process   │
│┌────────┐││┌────────┐││┌────────┐│
││ Thread ││││ Thread ││││ Thread ││
│└────────┘││└────────┘││└────────┘│
│┌────────┐││┌────────┐││┌────────┐│
││ Thread ││││ Thread ││││ Thread ││
│└────────┘││└────────┘││└────────┘│
└──────────┘└──────────┘└──────────┘

进程 VS 线程

进程和线程是包含关系,但是多任务既可以由多进程实现,也可以由单进程内的多线程实现,还可以混合多进程+多线程。具体使用哪种方式,要考虑到进程和线程的特点。

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

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

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

多线程

Java语言内置了多线程支持:一个Java程序实际上就是一个JVM进程,JVM进程用一个主线程来执行main()方法,在main()方法内部也可以启动多个线程。此外,JVM还有负责垃圾回收的其他工作线程。因此对于大多数Java程序来说,就是如何使用多线程实现多任务

和单线程相比,多线程编程的特点在于:多线程经常需要读写共享数据,并且需要同步。例如,播放电影时,就必须由一个线程播放视频,另一个线程播放音频,两个线程需要协调运行,否则画面和声音就不同步。因此,多线程编程的复杂度高,调试更困难。

Java多线程编程的特点又在于:

  • 多线程模型是Java程序最基本的并发模型;
  • 后续读写网络、数据库、Web开发等都依赖Java多线程模型。

创建新线程

Java语言内置了多线程支持。当Java程序启动的时候,实际上是启动了一个JVM进程,然后,JVM启动主线程来执行main()方法。在main()方法中,又可以启动其他线程。

要创建一个新线程很简单,只需要实例化一个Thread实例,然后调用它的start()方法。

源码链接

public class Main {
    public static void main(String[] args) {
        Thread t = new Thread();
        t.start(); // 启动新线程
    }
}

这个线程是极其简单的,实际上没有什么也不做就立刻结束了,实际使用中,我们希望新线程能够执行指定的代码,有以下多种方法。

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

    源码

    public class Main2 {
     public static void main(String[] args) {
         Thread thread = new MyThread();
         thread.start();
     }
    }
     
     class MyThread extends Thread {
         @Override
         public void run() {
             System.out.println("Start new Thread!");
         }
     }
    
  2. 创建Thread实例时,传入一个Runnable实例

    源码

    public class Main3 {
     public static void main(String[] args) {
         Thread thread = new Thread(new MyRunnable());
         thread.start();
     }
    }
     class MyRunnable implements Runnable {
         @Override
         public void run() {
             System.out.println("Start new Thread!");
         }
     }
    

能够体现线程执行的语句和main()方法执行的区别的代码

public class Main {
    public static void main(String[] args) {
        System.out.println("main start...");
        Thread t = new Thread() {
            public void run() {
                System.out.println("thread run...");
                System.out.println("thread end.");
            }
        };
        t.start();
        System.out.println("main end...");
    }
}

main线程执行的代码有4行,首先打印main start,然后创建Thread对象,紧接着调用start()启动新线程。当start()方法被调用时,JVM就创建了一个新线程,我们通过实例变量t来表示这个新线程对象,并开始执行。

接着,main线程继续执行打印main end语句,而t线程在main线程执行的同时会并发执行,打印thread runthread end语句。

run()方法结束时,新线程就结束了。而main()方法结束时,主线程也结束了。

我们再来看线程的执行顺序:

  1. main线程肯定是先打印main start,再打印main end
  2. t线程肯定是先打印thread run,再打印thread end

但是,除了可以肯定,main start会先打印外,main end打印在thread run之前、thread end之后或者之间,都无法确定。因为从t线程开始运行以后,两个线程就开始同时运行了,并且由操作系统调度,程序本身无法确定线程的调度顺序。

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

public class Main {
    public static void main(String[] args) {
        System.out.println("main start...");
        Thread t = new Thread() {
            public void run() {
                System.out.println("thread run...");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {}
                System.out.println("thread end.");
            }
        };
        t.start();
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {}
        System.out.println("main end...");
    }
}

sleep()传入的参数是毫秒。调整暂停时间的大小,我们可以看到main线程和t线程执行的先后顺序。

线程的优先级

可以对线程设定优先级,设定的方法是:

Thread.setPriority(int n) // 1~10, 默认值5

优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。

posted @ 2020-11-10 23:11  CNPolaris  阅读(72)  评论(0编辑  收藏  举报