什么是线程,线程和进程的关系_进程和线程的区别是什么

什么是线程,线程和进程的关系_进程和线程的区别是什么本为详细介绍了线程的概念,进程和线程之间的关系,以及线程的创建方法_什么是线程

一. 认识线程(Thread)

1.1 概念

1.1.1 什么是线程

进程进一步细化为线程, 是程序内部的一条执行路径. 一个进程中至少有一个线程. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间”同时”执行多份代码.

1.1.2 线程存在的意义

① “并发编程”成为”刚需”

  • 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU 资源.
  • 有些人物场景需要”等待IO”, 为了让等待IO的时间能去做一些其他的工作, 也需要用到并发编程.

② 虽然多进程也能实现并发编程, 但是线程比进程跟轻量.

  • 创建和销毁的速度更快
  • 调度的速度更快

③ 线程虽然比进程轻量, 但是人们还不满足, 于是又有了”线程池(ThreadPool)” 和 “协程(Coroutine)”.

1.1.3 进程和线程之间的区别和联系

  • 进程包含线程, 都是实现并发编程的方式. 每个进程至少有一个线程, 即主线程.
  • 进程和进程之间不共享内存空间,可以保持系统的稳定性; 同一个进程的线程之间共享同一个内存空间(操作系统的线程), 一旦一个线程出现异常, 可能会导致整个进程异常, 容易出现线程安全问题.
  • 一个PCB描述一个线程, 多个PCB描述一个进程. 一个进程中的所有PCB内的内存指针和文件描述符表是一样的, 但是上下文, 状态, 优先级等属性是不一样的. 这也说明了, 同一个进程中的线程共用同一份资源(内存 + 硬盘), 但是每个线程独立去CPU上调度.
  • 进程是操作系统进行资源分配的基本单位, 而线程是操作系统进行调度执行的基本单位. 创建进程的时候已经分配了资源, 后续创建进程, 直接共用之间的资源即可.

1.1.4 Java的线程和操作系统的线程

线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用.

Java中的线程是对系统级别的线程的封装和抽象.

操作系统级别下, 同一个进程的线程之间共享同一个内存空间;

而Java中的线程有私有空间.

在这里插入图片描述

1.2 创建线程

① 继承Thread类

  1. 创建一个线程类, 继承自Thread类

  2. 重写run方法: 线程入口方法, 描述线程执行的动作.

    class MyThread extends Thread { 
         
        @Override
        public void run() { 
         
            System.out.println("这里是线程运行的代码");
       }
    }
    
  3. 创建MyThread类的实例

    MyThread t = new MyThread();
    
  4. 调用 start 方法启动线程

    t.start(); // 线程开始运行
    
    • 这个操作就会在底层调用操作系统提供”创建线程”的API, 同时就会在操作系统内核里面创建出对应的pcb结构, 并且加入到对应的链表中.
    • 此时, 这个新创建出来的线程就会参与到CPU的调度中, 这个线程接下来要执行的工作, 就是刚刚上面重写的run方法.

代码解析:

class MyThread extends Thread { 
   
    @Override
    public void run() { 
   
        while (true) { 
   
            System.out.println("MyThread");
        }
   }
}
public class Main { 
   
    MyThread t = new MyThread();
    t.start();
    while (true) { 
   
            System.out.println("Main");
    }
}

第十行t.start()创建出了新的线程, 执行run()中的代码.

原来的主线程继续往下执行

主线程和新线程是并发执行的关系, 根据操作系统的调度执行.

注意:

  • 如果自己手动调用run()方法, 那么就只是普通方法, 没有启动多线程模式.
  • run()方法由JVM调用, 什么时候调用, 执行的时候都由操作系统的CPU调度决定.
  • 想要启动多线程, 必须调用start方法
  • 一个线程对象只能调用一次start()方法启动, 如果重复调用了, 则将抛出以上的异常”IllegalThreadStateException

② 实现Runnable 接口

  1. 实现Runnable接口

    class MyRunnable implements Runnable { 
         
        @Override
        public void run() { 
         
            System.out.println("这里是线程运行的代码");
       }
    }
    
  2. 创建Thread类的实例, 调用Thread的构造方法将Runnable对象作为参数

    Thread t = new Thread(new MyRunnable());
    
  3. 调用start方法

    t.start();
    

通过实现Runnable接口, 使得该类有了多线程的特征. 所有的分线程要执行的代码都在run方法里面.

在启动多线程的时候, 需要先通过Thread类的构造方法Thread(Runnable target)构造出对象, 然后调用Thread对象的start方法来运行多线程代码.

实际上, 所有的多线程代码都是通过运行Thread.start()来运行的, 因此, 不管是继承Thread类还是实现Runnable接口来实现多线程, 最终还是通过Thread的对象的API来控制流程的.

说明: Runnable对象仅仅作为Thread对象的target, Runnable实现类里包含的run()方法仅作为线程执行体. 而实际的线程对象依然是Thread实例, 只是该Thread线程负责执行其target的run()方法.

这一方法, 将线程要执行的任务和线程本身, 进一步解耦合了.

对比两种方法

联系:

Thread类实际上也是实现了Runnable接口的类

public class Thread extends Object implements Runnable

区别:

  • 继承Thread: 线程存放Thread子类run方法中.
  • 实现Runnable: 现成代码存放在接口的子类的run方法.

实现Runnable接口比继承Thread类所具有的优势

  • 避免了单继承的局限性
  • 多个线程可以共享同一个接口实现类的对象, 非常适合多个相同线程来处理同一份资源
  • 增加程序的健壮性, 实现解耦操作, 代码可以被多个线程共享, 代码和线程独立.

③ 变形写法

  1. 匿名内部类创建Thread子类的对象.

    public class Demo3{ 
         
        Thread t1 = new Thread() { 
         
            @Override
            public void run() { 
         
                System.out.println("使用匿名类创建 Thread 子类对象");
            }
        };
        t1.start();
    }
    

    说明:

    new Thread(): 创建一个子类, 继承自Thread, 这个子类是匿名的, 并且是在Demo3这个类的内部创建的.

    ② 在子类中重写了run方法

    ③ 创建了该子类的实例, 并且使用t1这个引用来指向.

  2. 匿名内部类创建Runnable子类的对象

    Thread t2 = new Thread(new Runnable() { 
         
        @Override
        public void run() { 
         
            System.out.println("使用匿名类创建 Runnable 子类对象");
        }
    });
    

    说明:

    new Runnable(): 创建一个类, 实现Runnable接口.

    ② 在类中重写了run方法

    ③ 创建了该类的实例, 并把这个实例作为参数传给了Thread的构造方法.

  3. lambda表达式创建Runnable子类对象

    Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
    Thread t4 = new Thread(() -> { 
         
        System.out.println("使用匿名类创建 Thread 子类对象");
    });
    

    ④ 其他写法

④ 其他写法

  • 基于Callable

    Callable<Integer> callable = new Callable<Integer>() { 
         
        @Override
        public Integer call() throws Exception { 
         
            //
            return null;
        }
    };
    FutureTask<Integer> futureTask = new FutureTask<>(callable);
    Thread thread = new Thread(futureTask);
    thread.start();
    Integer result = futureTask.get();
    
  • 基于线程池

    public static void main(String[] args) { 
         
        ExecutorService service = Executors.newFixedThreadPool(4);
        service.submit(new Runnable() { 
         
            @Override
            public void run() { 
         
                //
            }
        });
    }
    

    ExecutorService: 线程池对象

    Executors.newFixedThreadPool(int nThreads): 创建固定线程数量的线程池

    • Executors: 工厂类
    • newFixedThreadPool(int nThreads): 工厂类方法 创建固定线程数量的线程池
    • newCachedThreadPool(): 创建线程数量动态变化的线程池
    • newSingleThreadExecutor(): 创建单个线程的线程池
    • newScheduledThreadPool(int corePoolSize): 周期性线程池, 类似于定时器的效果
    • 上面四个方法都是对类ThreadPoolExecutor的封装, ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定.

    service.submit(Runnable task): 向线程池中添加任务.

1.3 查看线程

找到当前项目使用的jdk的位置, 并双击打开

什么是线程,线程和进程的关系_进程和线程的区别是什么

在这里插入图片描述

双击bin目录, 找到jconsole.exe

在这里插入图片描述

双击jconsole.exe, 会出现以下窗口

在这里插入图片描述

选择本地进程, 并找到当前运行的线程名称, 再点击连接

在这里插入图片描述

若出现以下窗口, 选择不安全连接.

在这里插入图片描述

出现以下窗口后, 点击线程

在这里插入图片描述

左下角就列出了所有的线程, 不仅有主线程和自己创建的线程, 还有JVM自带的线程.

在这里插入图片描述

点击某个线程, 就能得到该线程的具体信息

在这里插入图片描述

今天的文章什么是线程,线程和进程的关系_进程和线程的区别是什么分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/81758.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注