• 进程和线程详解


    一、进程和线程的概述

    1、要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在

    2、什么是进程?

          通过任务管理器我们就看到了进程的存在。 而通过观察,我们发现只有运行的程序才会出现进程。
          进程:就是正在运行的程序
          进程:是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。

    3、多进程有什么意义呢?

          单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。

          举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。 也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个程序。 可以提高CPU的使用率。

          问题: 一边玩游戏,一边听音乐是同时进行的吗?

                      不是。因为单核CPU在某一个时间点上只能做一件事情。 而我们在玩游戏,和在听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。

    4、什么是线程?

          在同一个进程(正在运行的程序)内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
          线程:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
          单线程:如果程序只有一条执行路径。

          多线程:如果程序有多条执行路径。 一个进程 = 一个正在运行的程序 = 1个线程+1个线程+1个线程+... = 多个线程 = 多个任务

     

    5、多线程有什么意义呢?

          多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率(提高能使用cpu的概率)。 程序的执行其实都是在抢CPU的资源,或者叫CPU的执行权。 多个进程是在抢这个资源,而其中的某一个进程如果执行路径(线程)比较多,那么就会有更高的几率抢到CPU的执行权。 我们是不敢保证哪一个线程能够在哪个时刻抢到cpu的执行权的,所以线程的执行具有随机性

    小结:
      多进程的意义:提高使用cpu的效率。(多用cpu)
      多线程的意义:提高能使用cpu的概率。(有多少机会用cpu,因为cpu不是你想用就能用啊)

    多线程程序的引入图解

    二、Java程序的运行原理及JVM的启动是多线程的吗?

      A:Java程序的运行原理
        Java通过java命令会启动java虚拟机。启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
         该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
      B:JVM的启动是多线程的吗?
        垃圾回收线程也要先启动,否则很容易会出现内存溢出
         JVM的启动是多线程的,因为它最低有两个线程启动了,主线程垃圾回收线程

    示例代码如下:

    1 package cn.itcast_01;
     2 /*
     3  *    进程:
     4  *        正在运行的程序,是系统进行资源分配和调用的独立单位。
     5  *        每一个进程都有它自己的内存空间和系统资源。
     6  *    线程:
     7  *        是进程中的单个顺序控制流,是一条执行路径。
     8  *        是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
     9  *
    10  *        一个进程如果只有一条执行路径,则称为单线程程序。
    11  *        一个进程如果有多条执行路径,则称为多线程程序。
    12  *
    13  *  举例:
    14  *      扫雷程序,迅雷下载
    15  *  
    16  *  大家注意两个词汇的区别:并行和并发。
    17  *        并行:前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
    18  *        并发:后者是物理上同时发生,指在某一个时间点同时运行多个程序。
    19  *
    20  *      
    21  *
    22  * Java程序的运行原理:
    23  *        通过java命令会启动 java虚拟机。启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。
    24  *        该进程会自动启动一个 “主线程”,然后主线程去调用某个类的 main方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
    25  * 
    26  * 思考题:
    27  *         jvm虚拟机的启动是单线程的还是多线程的?
    28  *             多线程的。
    29  *             原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。
    30  *             现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
    31  */
    32 public class MyThreadDemo {
    33     public static void main(String[] args) {
    34         System.out.println("hello");
    35         new Object(); // 造对象
    36         new Object(); // 造对象
    37         new Object(); // 造对象
    38         new Object(); // 造对象
    39         //...造很多很多对象后,如果垃圾回收线程不启动的话,内存就会溢出!
    40         System.out.println("world");
    41     }
    42 }
    

      

    3、多线程的实现方案(掌握)

    A:自定义类继承Thread类

    1:自定义类MyThread继承Thread类

    2:MyThread类里面重写run()方法

    3:在测测试类MyThreadTest中创建MyThread类的对象

    4:启动线程

    代码演示:

    package com.thread;
    
    public class ThreadDemo extends Thread {
        @Override
        public void run() {
            for (int i=0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"执行了"+i+"次");
            }
        }
    }
    

      

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

      

    运行结果:

    Thread-0执行了0次
    Thread-0执行了1次
    Thread-0执行了2次
    Thread-0执行了3次
    Thread-0执行了4次
    Thread-0执行了5次
    Thread-0执行了6次
    Thread-0执行了7次
    Thread-0执行了8次
    Thread-0执行了9次
    

      

    B:自定义类实现Runnable接口

    1:自定义类MyRunnable实现Runnable接口

    2:MyRunnable类里面重写run()方法

    3:在测测试类MyRunnableTest中创建MyRunnable类的对象

    4;在测测试类MyRunnableTest中再创建Thread类的对象,并把3步骤的对象作为构造参数进行传递

    5:启动线程

     代码演示:

    package com.thread;
    
    public class Runable1 implements Runnable{
        
        @Override
        public void run() {
            for(int i=0;i<5;i++){
                System.out.println(Thread.currentThread().getName()+"执行了"+i+"次");
            }
        }
    }
    

      

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

      运行结果:

    Thread-0执行了0次
    Thread-1执行了0次
    Thread-0执行了1次
    Thread-1执行了1次
    Thread-0执行了2次
    Thread-1执行了2次
    Thread-0执行了3次
    Thread-1执行了3次
    Thread-0执行了4次
    Thread-1执行了4次
    

      

    注意事项:
        1:Thread类的方法:
            public final String getName() 获取线程对象的名称(一般放在需要被线程执行的代run()方法里面)
            public final void setName(String name) 设置线程对象的名称
            对象名.setName("林青霞");
                        
        2:在不是Thread类的子类中,如何获取线程对象的名称呢?
            public static Thread currentThread() 返回当前正在执行的线程对象(静态方法)
            Thread.currentThread().getName()
                
        3:该自定义的类为什么要重写run()方法?
            自定义类中不是所有的代码都需要被线程执行。
            而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()方法,用来包含那些需要被线程执行的代码。    
            注意:这里的  被线程执行 = 开一个新线程执行    
                
        4:由于自定义类实现了接口,所以就不能在自定义类中直接使用Thread类的getName()方法了,但是可以间接的使用。
            Thread.currentThread().getName()

       5:为什么要实现Runnable接口?
         -Java不支持多继承
    -不打算重写Thread类的其他方法
    小问题:
        1:为什么要重写run()方法?
            :run()方法里面封装的是被线程执行的代码。    
        2:启动线程对象用的是哪个方法?
            :start()方法
        3:run()方法和start()方法的区别?
            :run()方法直接调用仅仅是普通方法。
               start()方法是先启动线程,再由jvm去调用run()方法。
        4:有了方式1,为什么还来一个方式2呢?
            :若自定义类MyThread类已经有一个父类了,那么它就不可以再去继承Thread类了。(java不支持多继承)
               若自定义类MyRunnable类已经实现了一个接口了,那么它还可以再去实现Runnable接口。(java支持多实现)
               即可以避免由于Java单继承带来的局限性。
                
               在测试类MyThreadTest中,要想开多个线程,就要先new多个自定义类MyThread的对象,每一个自定义类MyThread的对象的成员变量都相同,这样需要在栈中开辟很多内存;
               在测试类MyRunnableTest中,要想开多个线程,只需要new一个自定义类MyRunnable的对象,再new多个Thread类的对象即可,这样就大大节约了内存。
               即适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码和数据有效分离(即耦合性降低),较好的体现了Java面向对象的设计思想

    4、线程的调度模型和如何获取和设置线程优先级

          假如我们的计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。 那么Java是如何对线程进行调用的呢?

    线程有两种调度模型。

    A:线程的调度模型

            a:分时调度模型 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。

            b:抢占式调度模型 (Java采用的是该调度方式) 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片的概率相对高一些。

    B:如何获取和设置线程优先级

             线程默认的优先级是:5。

             线程优先级的范围是:1-10。

    如何获取线程对象的优先级?

             public final int getPriority()

             返回线程对象的优先级 int i = 对象名.getPriority();

    如何设置线程对象的优先级?

             public final void setPriority(int newPriority) 更改线程的优先级 对象名.setPriority(10); IllegalArgumentException:非法参数异常 抛出的异常表明向方法传递了一个不合法或不正确的参数。

     5、线程的控制(即线程常见的方法)

    A:线程休眠

         public static void sleep(long millis)    单位是毫秒(该方法会抛出异常)
         Thread.sleep(1000);
    package com.thread;
    
    public class Sleep implements Runnable{
       
        @Override
        public void run() {
            for (int i=0;i<10;i++){
                if (i==5){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"执行了"+i+"次");
            }
       }
    }
    
    
    

      

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

      



    B:线程加入 public final void join() 等待该线程终止(为了使某线程先执行完毕)(该方法会抛出异常) 对象名.join(); // 该方法必须在启动线程后调用
    package com.join;
    
    public class MyThread extends Thread{
    
        @Override
        public void run() {
            for (int i=0;i<10;i++){
                System.out.println(this.getName()+"执行了"+i+"次");
            }
        }
    }
    


    class JoinDemo {
       
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            myThread.start();
    
            try {
                myThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            for (int i=0;i<5;i++){
                System.out.println("主线程执行了"+i+"次");
            }
    
            System.out.println("主线程执行结束");
        }
    }
    

      

      运行结果:

    Thread-0执行了0次
    Thread-0执行了1次
    Thread-0执行了2次
    Thread-0执行了3次
    Thread-0执行了4次
    Thread-0执行了5次
    Thread-0执行了6次
    Thread-0执行了7次
    Thread-0执行了8次
    Thread-0执行了9次
    主线程执行了0次
    主线程执行了1次
    主线程执行了2次
    主线程执行了3次
    主线程执行了4次
    主线程执行结束
    

      


    C:线程礼让
    public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 能够在一定的程度上,让多个线程的执行更和谐,但是不能靠它保证一个线程一次。 Thread.yield(); D:后台线程(守护线程/用户线程) public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程 对象名.setDaemon(true); // 设置守护线程 当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用。 E:中断(终止)线程(掌握) public final void stop() 让线程停止,过时了,但是还可以使用。(为什么会过时呢?因为该方法太暴力了,具有固有的不安全性,直接把线程停止,该线程之后的代码都不能执行了) 对象名.stop(); public void interrupt() 中断线程。 把线程的状态终止,并抛出一个InterruptedException异常。 对象名.interrupt(); 注意事项: 如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能try,不能throws。

    6、线程的状态和生命周期

    A:线程的创建 创建线程对象,无资格无权。

    B:线程的就绪 有资格无权

    C:线程的运行 有资格有权

    D:线程的阻塞 无资格无权

    E:线程的死亡 无资格无权




    
    
  • 相关阅读:
    高斯消元(模板及bitset优化异或方程)
    dsu on tree
    拉格朗日插值
    [CF] CF900D Unusual Sequences
    【模板】Polya 定理
    Min-25筛学习笔记
    [CF] CF156C Cipher
    基于 Flink + Kafka 的广告实时数据分析建设与实践
    开源中国【面经】Java后台开发
    spring boot中连接数据库报错500(mybatis)
  • 原文地址:https://www.cnblogs.com/zyx110/p/10820315.html
Copyright © 2020-2023  润新知