进程和线程的比较

发布时间:2024年01月13日

目录

一、前言

二、Linux查看进程、线程

2.1 Linux最大进程数

2.2 Linux最大线程数

2.3 Linux下CPU利用率高的排查

三、线程的实现

四、上下文切换

五、总结


一、前言

进程是程序执行相关资源(CPU、内存、磁盘等)分配的最小单元,是一系列线程的集合,进程之间相互独立,有自己的内存空间;线程是CPU资源分配的最小单元,线程需要的资源更少,可以看做是一种轻量级的进程,线程会共享进程中的内存,但线程使用独立的栈、程序计数器,线程相互通信更加方便。

在项目开发中,经常会用到线程以及多线程功能来实现异步任务处理等。项目上线之后,如果出现服务CPU高的异常情况,那么这个时候就需要借助Linux(因为一般情况服务都是使用Linux)查看进程、线程来定位最终的问题。

二、Linux查看进程、线程

2.1 Linux最大进程数

Linux中进程可创建的实际值通过进程标识值(process identification value)-PID来标示,可以使用

cat /proc/sys/kernel/pid_max 查看系统中可以创建的进程数实际值

可以使用ulimit命令修改最大限制值,

ulimit -u 1024

?如果要修改kernel.pid_max的值,需要使用

sysctl -w kernel.pid_max=1024

2.2 Linux最大线程数

用ulimit -s可以查看默认的线程栈大小,一般情况下,这个值是8M=8192KB

不过Java程序受JVM堆空间的限制,比如以下代码

public class ThreadExample extends Thread{
    public static void main(String[] args) {

        for(int i = 0; i < 100000; i++){
            ThreadExample myThread = new ThreadExample(i);
            myThread.start();
        }
    }

    private Integer threadNo;

    ThreadExample(Integer threadNo){
        threadNo = threadNo;
        System.out.println("ThreadNo = " + threadNo);
    }

    @Override
    public void run(){
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在idea上设置-Xmx1m,启动运行程序,创建出部分线程后,会报OutOfMemoryError错误

2.3 Linux下CPU使用率高的排查

示例代码如下,这段代码可以明显判断出来在while(true){count++;}的地方,会占用很高的CPU使用率,那么如果代码已经上线了,在生产上,我们如何来判断哪里出问题了呢?

public class CpuRatioExample extends Thread{

    private Integer count = 0;

    public static void main(String[] args) {
        CpuRatioExample cpuRatioExample = new CpuRatioExample();
        cpuRatioExample.start();
    }

    @Override
    public void run(){
        while(true){
            count++;
        }
    }
}

第一步:运行编译后的class

java CpuRatioExample

程序运行之后,我们发现CPU使用率过高,这个时候,我们需要排查是哪个代码导致的,一般情况生产系统上都会做CPU、磁盘等基础设施的监控。

第二步:CPU使用率过高排查

top 命令查看哪个进程CPU使用率高

使用top命令发现 PID 1822的CPU占用异常,再进一步查找哪个线程导致的,

top -H -p pid 可以查看哪个线程cpu过高

第三步:使用jstack命令保存栈信息

jstack 1822 > 1822.stack

并分析栈信息,查找 1878线程对应的栈信息

?stack信息是以16进制显示的, 所以需要将CPU使用率高的线程1878转换为十六进制 756;定位到在CpuRationExample的17行代码运行,结合源代码,定位了最终问题。?

?

三、线程的实现

3.1 单线程的实现方式

3.1.1?Thread

public class ThreadExample extends Thread{
    public static void main(String[] args) {

        for(int i = 0; i < 100000; i++){
            ThreadExample myThread = new ThreadExample(i);
            myThread.start();
        }
    }

    private Integer threadNo;

    ThreadExample(Integer threadNo){
        threadNo = threadNo;
        System.out.println("ThreadNo = " + threadNo);
    }

    @Override
    public void run(){
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这种方式很简单,但是不支持多重继承,所以不能继承其他类。?

3.1.2?Runnable

public class ThreadExample implements Runnable{
    public static void main(String[] args) {

        for(int i = 0; i < 100000; i++){
            ThreadExample myThread = new ThreadExample(i);
            new Thread(myThread).start();
        }
    }

    private Integer threadNo;

    ThreadExample(Integer threadNo){
        threadNo = threadNo;
        System.out.println("ThreadNo = " + threadNo);
    }

    @Override
    public void run(){
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

?这种方式比继承Thread类更灵活,因为一个类可以实现多个接口。

3.1.3 FetureTask

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadExample {
    public static void main(String[] args) {

        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
            // 异步执行的任务
            return 1;
        });
        new Thread(futureTask).start();
        try {
            // 获取异步执行的结果
            int result = futureTask.get();
            System.out.println("result = " + result);
        } catch (InterruptedException | ExecutionException e) {
            // 处理异常
        }
    }
}

3.2 线程池的实现方式

有关线程池的,后续再详细介绍。

3.2.1 使用Executors类创建线程池

Executors.newFixedThreadPool(int nThreads):创建一个固定大小的线程池。
Executors.newCachedThreadPool():创建一个可以缓存线程的线程池。
Executors.newSingleThreadExecutor():创建一个单线程化的线程池。

3.2.2 使用ThreadPoolExecutor类创建线程池

ThreadPoolExecutor是一个更底层的类,允许开发者更精细地控制线程池的行为,比如:

ThreadPoolExecutor executor = new ThreadPoolExecutor(  
    5, // corePoolSize  
    10, // maximumPoolSize  
    60L, // keepAliveTime  
    TimeUnit.SECONDS, // unit  
    new LinkedBlockingQueue<Runnable>() // workQueue  
);

四、上下文切换

多线程和单线程的选择往往取决于具体的应用场景和需求,单线程是一次只做一件事,按照顺序执行,而多线程可以同时处理多个任务,抢占更多的系统资源,但是也会出现上下文切换,有些时候,多线程的性能未必比单线程要好。比如以下这段代码

public class DemoApplication {
    public static void main(String[] args) {
        //运行多线程
        MultiThreadTester test1 = new MultiThreadTester();
        test1.Start();
        //运行单线程
        SerialTester test2 = new SerialTester();
        test2.Start();
    }


    static class MultiThreadTester extends ThreadContextSwitchTester {
        @Override
        public void Start() {
            long start = System.currentTimeMillis();
            MyRunnable myRunnable1 = new MyRunnable();
            Thread[] threads = new Thread[3];
            //创建多个线程
            for (int i = 0; i < 3; i++) {
                threads[i] = new Thread(myRunnable1);
                threads[i].start();
            }
            for (int i = 0; i < 3; i++) {
                try {
                    //等待一起运行完
                    threads[i].join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            long end = System.currentTimeMillis();
            System.out.println("multi thread exce time: " + (end - start) + "ms");
            System.out.println("counter: " + counter);
        }
        // 创建一个实现Runnable的类
        class MyRunnable implements Runnable {
            public void run() {
                while (counter < 100000000) {
                    synchronized (this) {
                        if(counter < 100000000) {
                            increaseCounter();
                        }

                    }
                }
            }
        }
    }

    //创建一个单线程
    static class SerialTester extends ThreadContextSwitchTester{
        @Override
        public void Start() {
            long start = System.currentTimeMillis();
            for (long i = 0; i < count; i++) {
                increaseCounter();
            }
            long end = System.currentTimeMillis();
            System.out.println("serial exec time: " + (end - start) + "ms");
            System.out.println("counter: " + counter);
        }
    }

    //父类
    static abstract class ThreadContextSwitchTester {
        public static final int count = 100000000;
        public volatile int counter = 0;
        public int getCount() {
            return this.counter;
        }
        public void increaseCounter() {

            this.counter += 1;
        }
        public abstract void Start();
    }
}

这段代码的测试结果是,单线程的性能高于多线程的性能,其主要原因就是多线程的上下文切换导致性能降低。

如果想要进一步分析上下文切换情况,可以使用vmstat和pidstat分析上下文切换情况。

五、总结

本文介绍了进程和线程的区别以及Java如何开发单线程、多线程;linux下最大进程数、线程数的限制,以及如何通过jstack排查CPU使用率高的问题。后续将专门针对多线程开发进行介绍。

文章来源:https://blog.csdn.net/flyingcloude/article/details/135539095
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。