美文网首页
进程、线程、线程池

进程、线程、线程池

作者: DeppWang | 来源:发表于2019-05-27 22:41 被阅读0次

注:以下大部分内容和图片来自码农翻身公众号(用故事讲解技术)。

进程

程序即进程,是拥有资源的基本单位,是线程的容器。

单个CPU一次只能运行一个进程。在单核系统中,得益于CPU的高速度,很多程序在短时间内不断的切换,在外界看来,似乎多个程序在同时执行。

比如:让有道云笔记上运行几十毫秒,然后打断,让Chrome运行几十毫秒,再打断,再让微信也运行几十毫秒,如此循环往复。

正在运行的进程被操作系统上下文切换时,将工作现场保存到进程控制块(Processing Control Block, PCB),进入阻塞队列,再进入就绪队列,最后才能再次运行。

现在电脑都是多核了,比如Intel i5-7200U,内核数2、线程数4。代表有2个CPU,CPU支持超线程技术,一个CPU当两个用,可同时运行4个进程。

一个文字处理程序(如有道云笔记),进程中至少需要有两个线程,一个负责和用户交互,另外一个专门负责定时的自动保存,IO导致的阻塞就不会影响另外一个了。

线程共享进程中的代码、数据等资源,但也有自己独特的一部分。

image.png
  • 只在进程内部实现线程表:操作系统不知道进程中的线程,一旦出现线程阻塞的系统调用,不仅仅阻塞那个线程,还会阻塞整个进程!
  • 只在内核中维护线程表:创建线程经过内核,慢。

混合使用:用户空间的进程可以创建线程(用户线程), 内核也会创建线程(内核线程),用户线程映射到内核线程上。

image.png

线程

所谓线程,就是程序代码的执行,一个进程至少得有一个线程,要不然,这个进程怎么运行?

进程是拥有资源的基本单位,线程是CPU调度的基本单位。

操作系统在做调度的时候,基本单位不是Word,QQ音乐这样的进程,而是T1,T2,T3,T4这些线程。CPU给线程分配时间片。

image.png

JVM是java.exe运行起来,是进程。Java程序运行在JVM当中,JVM这个进程其实就是他们的容器。所以Java是多线程编程,不是多进程编程。

不同系统的JVM源码不同,但是JVM的作用是一致的:提供一个容器,让Java程序可以在不同的操作系统上的JVM中运行。这就是JVM的可移植性,在Mac/Windows上开发的程序可以不加修改地放到服务器Linux上去运行。

p直接调用run()方法,此时p只是一个代表一个对象,还不是线程,相当于当前线程执行p调用它的一个方法而已,没有将新线程创建成功。

class PrimeThread extends Thread {
    long minPrime;
    PrimeThread(long minPrime) {
        this.minPrime = minPrime;
    }
 
    public void run() {
        // compute primes larger than minPrime
         . . .
    }
}

PrimeThread p = new PrimeThread(143);
p.start();

执行start()方法,是创建新线程的准备工作:设置线程的上下文。比如线程的栈(用于函数调用),线程的状态,线程的PC(程序计数器)等等。准备工作完成后,线程才可以被调度,会自动执行run()。

线程池

  • 虽然线程是轻量级,如果每个用户的情节都创建一个线程,服务器也难以承受。
  • 众多线程竞争CPU,不断切换,CPU调度不堪重负(上下文切换的开销对效率的影响),很多线程也不等不等待。
  • 前辈们的思路就是使用线程池,(1)用少量的线程(2)让线程保持忙碌
image.png

线程池中创建一定数量的线程,让这些线程去处理所有的任务,任务执行完了以后,线程并不结束,而是回到线程池中去,等待接受下一个任务。

image.png

阻塞队列的特性:(消费者)线程调用它的take()方法取数据时,如果这个Queue中没有数据,该线程会阻塞;一个(生产者)线程调用它的put方法放数据时,如果Queue满了,也会阻塞。

可以预先创建线程池中的线程,线程刚创建时,就让他们调用take()方法,使其进入阻塞状态,任务来了就不用临时再创建,可以立刻开始服务。

线程池中的Worker线程:
public class WorkerThread extends Thread {

    private BlockingQueue<Task> taskQueue = null;
    private boolean isStopped = false;
    //持有一个BlockingQueue的实例
    public WorkerThread(BlockingQueue<Task> queue){
        taskQueue = queue;
    }

    public void run(){
        while(!isStopped()){
            try{
                Task task = taskQueue.take();
                task.execute();
            } catch(Exception e){
                //log or otherwise report exception,
                //but keep pool thread alive.
            }
        }
    }
    ......略......
}

线程池每个线程run()方法中设置一个循环,每次都尝试从BlockingQueue中获取任务,如果任务为空,就会被阻塞等待,如果有任务来了,所有没有干活的线程一起抢,先加锁的处理,处理完了以后,依然试图从BlockingQueue中获取任务,就这么依次循环下去。

Doug Lea大师封装了一套实现:

ExecutorService executorService = Executors.newFixedThreadPool(10);

executorService.execute(new Runnable() {
    public void run() {
        System.out.println("Asynchronous task");
    }
});

executorService.shutdown();

execute()使用了可重入锁ReentrantLock。

总结

线程的自述:首先,从线程池进入就绪车间,在就绪车间你不知道什么时候会被CPU挑中执行;第二,在执行的过程中随时可能被打断(时间片到期),让出CPU车间,进入阻塞车间;第三,一旦出现硬盘、数据库这样耗时的操作,也得让出CPU去阻塞车间等待;第四,就是数据来了,进入就绪车间后,你也不一定马上执行,还得等着CPU挑选;第五,执行完了,再回到线程池中。

image.png image.png

参考

相关文章

网友评论

      本文标题:进程、线程、线程池

      本文链接:https://www.haomeiwen.com/subject/sccltctx.html