MENU

进程和线程

April 15, 2021 • Read: 96 • 教程学习

什么是进程?

首先,从操作系统的层次来说,进程(Progress)是资源分配和系统调度的的基本单位也可以理解为程序的基本执行实体;当一个程序被载入到内存中并准备执行,它就是一个进程!当进程被创建了,操作系统就会为该进程分配一个唯一、不重复的ID,用于区分不同的进程

什么是线程?

线程(Thread)是CPU调度的最小单位,是程序执行流的最小单位,线程不能独立的拥有资源(应该由多个线程共享),创建线程的开销要比进程小很多,因为创建线程仅仅需要堆栈指针程序计数器就可以了,而创建进程需要操作系统分配新的地址空间,数据资源等,这个开销比较大。每一个进程(程序)都至少有一个线程,进程是线程的容器,在单个程序中同时运行多个线程完成不同的工作,称为多线程!

进程和线程的区别

进程和线程的区别可以归纳为以下的几点

  • 同一个进程可以包含几个线程,一个线程中至少包含一个线程,一个线程只能存在于一个进程中。即线程必须依托于进程
  • 同一个进程下的各个线程并不是相互独立的,需要共享进程的资源。而各个进程基本上独立,并不相互干扰
  • 线程是轻量级的进程,它的创建和销毁所需要的时间和资源相比进程小得多
  • 在操作系统中,进程是可以拥有自己的资源,线程不能独立的拥有自己的资源。

进程的调度

在一般的操作系统中,用户使用的进程,如:QQ、音乐、浏览器等,这些用户进程数一般是多于CPU核数,这将导致它们在运行的过程中相互争夺CPU,这就要求操作系统有一定策略来分配进程。一般的有如下的五种常用的进程调度算法

  • 先来先服务算法

先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。当在作业调度中采用该算法时,每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。在进程调度中采用FCFS算法时,则每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配处理机,使之投入运行。该进程一直运行到完成或发生某事件而阻塞后才放弃处理机。

  • 短作业优先算法

短作业(进程)优先调度算法,是指对短作业或短进程优先调度的算法。它们可以分别用于作业调度和进程调度。短作业优先(SJF)的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。而短进程优先(SPF)调度算法则是从就绪队列中选出一个估计运行时间最短的进程,将处理机分配给它,使它立即执行并一直执行到完成,或发生某事件而被阻塞放弃处理机时再重新调度。

  • 时间片轮转法算法

系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把CPU分配给队首进程,并令其执行一个时间片。时间片的大小从几ms到几百ms。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证就绪队列中的所有进程在一给定的时间内均能获得一时间片的处理机执行时间。换言之,系统能在给定的时间内响应所有用户的请求。

  • 多级反馈队列调度算法

多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述:

1)应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,第i+1个队列的时间片要比第i个队列的时间片长一倍。

2)当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n队列便采取按时间片轮转的方式运行。

3)仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即第i队列中某个正在运行的进程的时间片用完后,由调度程序选择优先权较高的队列中的那一个进程,把处理机分配给它。

  • 优先权调度算法

此算法常被用于批处理系统中,作为作业调度算法,也作为多种操作系统中的进程调度算法,还可用于实时系统中。当把该算法用于作业调度时,系统将从后备队列中选择若干个优先权最高的作业装入内存。当用于进程调度时,该算法是把处理机分配给就绪队列中优先权最高的进程,这时,又可进一步把该算法分成如下两种。

  • 抢占式优先权调度算法

    在这种方式下,系统同样是把处理机分配给优先权最高的进程,使之执行。但在其执行期间,只要又出现了另一个其优先权更高的进程,进程调度程序就立即停止当前进程(原优先权最高的进程)的执行,重新将处理机分配给新到的优先权最高的进程。因此,在采用这种调度算法时,是每当系统中出现一个新的就绪进程i时,就将其优先权Pi与正在执行的进程j的优先权Pj进行比较。如果Pi≤Pj,原进程Pj便继续执行;但如果是Pi>Pj,则立即停止Pj的执行,做进程切换,使i进程投入执行。显然,这种抢占式的优先权调度算法能更好地满足紧迫作业的要求,故而常用于要求比较严格的实时系统中,以及对性能要求较高的批处理和分时系统中。

  • 抢占式优先权调度算法

    在这种方式下,系统同样是把处理机分配给优先权最高的进程,使之执行。但在其执行期间,只要又出现了另一个其优先权更高的进程,进程调度程序就立即停止当前进程(原优先权最高的进程)的执行,重新将处理机分配给新到的优先权最高的进程。因此,在采用这种调度算法时,是每当系统中出现一个新的就绪进程i时,就将其优先权Pi与正在执行的进程j的优先权Pj进行比较。如果Pi≤Pj,原进程Pj便继续执行;但如果是Pi>Pj,则立即停止Pj的执行,做进程切换,使i进程投入执行。显然,这种抢占式的优先权调度算法能更好地满足紧迫作业的要求,故而常用于要求比较严格的实时系统中,以及对性能要求较高的批处理和分时系统中。

Java默认线程

在一个Java程序中默认有几个线程?

2个,一个main函数运行起来,这个main函数中是一个单线程程序!但是这个所谓的单线程程序只是JVM这个程序中的一个线程,JVM本身是一个多线程的程序,除了这个主函数,还有GC线程(垃圾收集器线程)

Java真的能开启多线程吗?

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread();
        thread.start();
    }
}

上面是一个线程开启的实例代码,那Java真的能开启多线程吗?查看这个start()方法的实现!如下:

image-20210415203540198

start()方法是通过调用方法本地start0()从而开启多线程的,其原理还是调用了C++的方法从而开启一个线程,Java是无法直接调用硬件的!

线程的状态

线程的一个getState()能返回一个State的枚举类型如下:

public enum State {
    
    NEW,        // 新生

    
    RUNNABLE,    // 运行

    
    BLOCKED,    // 阻塞

    
    WAITING,    // 等待        

  
    TIMED_WAITING,// 超时等待

   
    TERMINATED;    // 终止
}

即Java有如上的几种状态。

Waiting和Sleep的区别

虽然 wait 和 sleep 都能将线程状态变成等待状态,但是它们在行为和使用方式上完全不一样的。

  • 使用的位置不同

wait()必须在正在同步代码块中使用,如synchronizedLock中使用;而sleep()方法不需要再同步条件下调用,你可以任意正常的使用。

  • 所在的类不同

wait()方法用于和定义于Object类的,而sleep方法操作于当前线程,定义在java.lang.Thread类里面。

  • 释放锁的方式不同

调用wait()的时候方法会释放当前持有的锁,而sleep方法不会释放任何锁(抱着锁睡觉)

Java实现多线程的方式

1、继承Thread类

public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println("new thread");
    }
}

通过继承 Thread 类,并重写它的 run 方法,我们就可以创建一个线程。

  • 首先定义一个类来继承 Thread 类,重写 run 方法。
  • 然后创建这个子类对象,并调用 start 方法启动线程。

2、实现Runnable接口

public class Test {
    public static void main(String[] args) {
        MyThread thread =new MyThread();
        new Thread(thread).start();
    }
}
class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println("new Thread");
    }
}

通过实现 Runnable ,并实现 run 方法,也可以创建一个线程。

  • 首先定义一个类实现 Runnable 接口,并实现 run 方法。
  • 然后创建 Runnable 实现类对象,并把它作为 target 传入 Thread 的构造函数中
  • 最后调用 start 方法启动线程。

上面的代码并不是很好,这样会降低线程类的耦合性,可以使用如下的Lambda表达式创建线程(推荐使用

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread = new Thread(() -> { // 这里使用lambda表达式
            myThread.print();
        });
        thread.start();
    }
}

class MyThread {
    public void print() {
        System.out.println("我是线程类");
    }
}

3、实现Callable接口

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> task = new FutureTask<>(new MyThread());
        new Thread(task).start();
        Integer result = task.get(); // 获取线程的指向结果,阻塞式
        System.out.println(result);

    }
}

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        return new Random().nextInt(100);
    }
}
  • 首先定义一个 Callable 的实现类,并实现 call 方法。call 方法是带返回值的。
  • 然后通过 FutureTask 的构造方法,把这个 Callable 实现类传进去。
  • 把 FutureTask 作为 Thread 类的 target ,创建 Thread 线程对象。
  • 通过 FutureTask 的 get 方法获取线程的执行结果。

4、线程池创建

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i=0;i<10;i++){
            executorService.execute(new MyThread());
        }
        executorService.shutdown();

    }
}

class MyThread implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"=>执行了");
    }
}

此处用 JDK 自带的 Executors 来创建线程池对象。

  • 首先,定一个 Runnable 的实现类,重写 run 方法。
  • 然后创建一个拥有固定线程数的线程池。
  • 最后通过 ExecutorService 对象的 execute 方法传入线程对象。

并发和并行

  • 并发:一个处理器同时处理多个任务
  • 并行:多个处理器或者多核的处理器同时处理多个不同的任务

前者是逻辑上的同时发生,而后者是物理上的同时发生

  • 并发性(concurrency),又称共行性,是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。
  • 并行(parallelism)是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行。

img

版权声明:文章转载请注明来源,如有侵权请联系删除!

Archives QR Code Tip
QR Code for this page
Tipping QR Code