0


Java Web 01_day

一.认识cpu:

    通过一些基础门电路组成的,cpu为什么跑得快? -> cpu集成程度比较高

   ![](https://i-blog.csdnimg.cn/direct/c3ebfb6a955a4c8c81dee0104cee365b.png)

    2.50GHz叫做cpu的主频,数字越大算的越快,表示1s执行25亿条指令

二.操作系统

1.简介操作系统

    操作系统是一个“软件”->是电脑上最重要,最复杂的软件之一

    操作系统是一个“管理的软件”:

            1.对下,管理好各种硬件设备

            2.对上,给各种软件提供一个稳定的运行环境

            重点介绍,操作系统对“进程”这个软件资源的管理。

2. 进程

(1)什么是进程?

    进程是资源分配的最小单位,进程就是跑起来的程序,steam.exe,qq.exe都是可执行文件,静静的躺在硬盘上,不会对系统有任何影响,一单双击exe,操作系统就会把exe文件加载到内存上,并让cpu执行exe内部的一些指令,这个时候就把exe执行起来,开始执行一些具体的操作。

    这些运行起来的可执行的文件,成为“进程”

        

     Java代码最终都是通过java进程跑起来的,(此处的java进程就是常说的JVM)

(2)线程是什么?

    线程是进程内部的一个部分,进程包含线程

(3)操作系统如何管理进程

    1.先描述一个进程(明确出一个进程上面的一些相关属性)PCB(进程控制块)

    2.再组织诺干个进程(使用数据结构,将这些PCB组织到一起,方便进行增删改查)

      典型的案例就是使用双向链表将PCB组织起来(Linux)

      所谓的创建进程。就是先创建出PCB,然后将PCB加入到双向链表

(4)PCB

想要一个进程正常工作,就需要给这个进程分配一些系统资源:内存,硬盘,CPU

PCB的基础属性:

    1.PID :进程的身份标识

    2.内存指针:指明了这个进程要执行的指令在内存的哪里,以及这个进程执行中以来的数据在哪里。 补充:每当运行一个exe,操作系统就会把这个exe加载到内存中,变成进程,此时进程的指令,数据都在内存中

    3.文件描述符表:程序运行过程中,经常要和文件打交道(文件是在硬盘上的)进程每次打开一个文件,就会在文件描述符表上多增加一项(这个文件描述符表可以视为一个数组,里面的每个原色又是一个结构体,对应一个文件的相关信息)。补充:一个进程只要一启动,不管是否对文件进行操作,都会打开三个文件(标准输入System.in,标准输出System.out,标准错误System.err)

    上面的属性是基础属性,下面的属性主要是为了实现进程调度:

    状态:就绪、运行、挂起、停止等状态。

    优先级:有静态优先级(进程创建后优先级不会改变)和动态优先级

    上下文:表示了上次进程被调出CPU的时候,程序的执行环境,下次进程上CPU时,可以恢复到之前的环境,然后继续执行

    记账信息:统计每个进程,都分别被执行了多久

(5)进程调度

    所谓的调度,就是时间管理

    **先来先服务(FCFS)调度算法**:它既可以用于作业的调度,又可以用于进程调度。FCFS属于不可剥夺(抢占)算法。从表面上看,它对所有作业都是公平的

** 短作业优先(SJF)调度算法:**该算法对长作业不利,SJF中长作业的周转时间会增加。更糟的是,若一旦有长作业进入系统的后备队列,由于调度程序总是优先调度那些短作业(即使是后来的短作业也会被优先安排给处理机),导致长作业长期不被调度,饿死在后备队列中。

** 优先级调度算法:**1.非剥夺(抢占)式优先级调度算法:当一个进程正在处理机上运行时,即使有某个更在重要或者紧迫的进程进入就绪队列,仍然让正在运行的进程继续运行,直到由于自身的原因而主动让出处理机时(任务完成或等待),才把处理机分配给更重要或紧迫的进程。
2.剥夺式优先级调度算法:当一个进程正在处理机上运行,若有某个更为重要或紧迫的进程进入就绪队列,则立即暂停正在运行的进程,将处理机分配给更重要或紧迫的进程。
而根据进程创建后其优先级是否可以改变,可以将进程优先级分为静态优先级和动态优先级。

** 高响应比优先调度算法:**响应比Rp = (等待时间+要求服务时间)/要求服务时间

** 时间片轮转调度算法:**在时间片轮转的调度算法中,时间片的大小对系统性能有很大影响。

    **多级反馈队列调度算法:**

(6)进程内存

     操作系统如何分配进程的内存?

    为了保证进程的独立性->“虚拟地址空间”->进程通过虚拟地址空间,已经各自隔离开了

    但是实际工作中,进程之间还是需要相互交互的,操作系统会提供“公共空间”->进程间通信

    操作系统中提供了各种各样的“公共空间”,操作系统中提供了多种这样的进程间通信机制,现在最主要使用的进程间通信方式:文件操作、网络操作(Socket)

三.多线程

1.线程是什么?

(1)线程的出现

    为什么要有进程-> 因为我们支持多任务,程序员也需要“并发编程”

    通过多进程完全可以实现并发编程,但是有问题->1.如果需要频繁的创建、销毁资源,成本较高->创建进程既要分配资源,销毁进程也要释放资源

                                                                                   2.频繁进行调度进程,成本较高

    如何解决进程的这个问题?

    1.进程池->进程池消耗的系统资源太多了

    2.使用线程实现并发编程->因为线程比进程给更轻量,每个线程也能执行一个任务,创建、销毁、调度线程的成本比进程更低,正因如此在Linux上也把线程成为轻量级的进程(LWP ->light weight process)

(2)线程—轻量级进程

    进程重在哪? ->进程资源申请和释放

    线程是包含在进程中的,**一个进程**中有**多个线程**共用同一份资源

    只是创建第一个线程的时候(需要资源分配)成本是较高的,在后续这个进程中其他线程的创建成本都会更低一些,因为不需要在分配资源了

经典面试题:进程和线程的区别和联系:

    1.进程包含线程,一个进程可以有一个线程,也可以有多个线程

    2.进程和线程都是为了处理并发编程这样的场景

      但是进程有问题,需要频繁分配释放资源,成本太高,相比之下线程更加轻量

    3.操作系统创建进程,给进程分配资源,进程是资源分配的最小单位

     操作系统创建的线程,是要在CPU上调度执行,线程是操作系统调度的最小单位

    4.进程具有独立性,每个进程有各自的虚拟存储空间,一个进程挂了,不会耽误其他进程

       一个进程包含若干线程,共用一片内存,一个线程挂了可能会影响其他进程,甚至导致整个进程崩溃

2.多线程编程

    **Thread类和Runnable接口:**

    Java标准库中提供了一个Thread类,来表示/操作线程。创建好的Thread实例,其实和操作系统中的线程是一一对应的关系,Thread类实现了Runnable接口,Runnable接口是描述了一个任务。

(1)最基本创建线程的方法:

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("test");
    }
}
//最基本的创建线程的方法
public class demo1 {
    public static void main(String[] args) {
        Thread t=new MyThread();
        t.start();
    }
}
    写一个子类MyThread继承Thread类,重写run方法(run方法是描述了这个线程内部要执行那些代码,每个线程是并发执行的,因此就需要告诉线程执行的代码是什么)。并不是定义好这个类,写好方法就创建线程了,需要调用start方法才真正在系统中创建了线程,才真正执行run方法。

    线程之间是并发执行的,上述例子看不出并发执行的效果

(2)main线程和t线程并发执行

class MyThread1 extends Thread{
    @Override
    public void run() {
        while(true) {
            System.out.println("test");
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class demo2 {
    public static void main(String[] args) {
        Thread t=new MyThread1();
        t.start();
        while(true){
            System.out.println("main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
    sleep函数:Thread.sleep(1000)->加限制让线程休眠一秒再打印

    中断异常:InterruptedException ->线程被强行中断的异常,受查异常

    在一个进程中至少会有一个进程,在一个java进程中,至少会有一个main方法的线程 ,自己创建的t线程和自动创建的main线程,就是并发执行的关系。代码运行结果:

    当t线程和main线程休眠时间到了,先唤醒哪个线程是不确定的,操作系统来说,内部对现成的调度顺序,在宏观上认为是随机的(抢占式执行)

(3)创建一个类,实现Runable接口,再创建Runable实例传给thread实例

class MyRunable implements Runnable{
    @Override
    public void run() {
        System.out.println("test");
    }
}
public class demo3 {
    public static void main(String[] args) {
        Thread t=new Thread(new MyRunable());
        t.start();
    }
}
    通过Runable描述任务的内容,进一步再把描述好的任务传给Thread实例

    再使用Thread的构造方法,进行线程的创建

(4)就是上面写法的翻版,使用匿名内部类

public class demo4 {
    public static void main(String[] args) {
        //匿名内部类
        Thread t=new Thread(){
            @Override
            public void run() {
                System.out.println("test");
            }
        };
            t.start();    
    }
}
    创建了一个匿名内部类,继承自Thread类,同时重写run方法,同时在new出这个匿名内部类的实例
public class demo5 {
    public static void main(String[] args) {
        Thread t=new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("test");
            }
        });
        t.start();
    }
}
    创建一个实现Runnable接口的匿名内部类,重写run方法再new出实例传给Thread。

创建线程总结:我们通过认为Runnable这种写法更好一点,能够做到让线程和线程执行的任务,更好的解耦,Runnable只是单纯的描述了一个任务,至于这个任务让谁执行和Runnable没关系,但是使用Thread是直接创建了线程,并安排了线程中的任务。

(5)使用lambda代替了Runnable

public class demo6 {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            System.out.println("test");
        });
        t.start();
    }
}

3.多线程的效率

    测试:将两个数字a和b自增到1000000000,并行执行的时候,时间大概是600多ms,两个线程并发执行时间大概是400ms,提升了接近50%。

4.线程的其他属性和方法

(1)Thread(String name)和jconsole

    这个构造方法是给线程取一个名字,可以使用jconsole来观察现成的名字

    jsconsole路径:          ![](https://i-blog.csdnimg.cn/direct/f83cde96f5504266ad8b74b94ed1fd4b.png)

    hzh是我们创建的线程:

    显示这个代码执行到哪里:

(2)是否是后台线程isDaemon

    如果线程是后台线程,就不影响进程退出,如果线程是前台线程,就会影响进程退出

    创建的t线程是前台线程,就算main执行完毕,进程也不能退出

(3)操作系统中对应的线程是否正在运行isAlive

    Thread t对象的生命周期和内核中对应的线程,生命周期并不一致

    创建t对象之后,调用start之前系统是没有线程的,在run执行完之后,线程就被销毁了,但是这个t对象可能还存在,通过isAlive就能判定线程的运行情况

    在调用start之后,run结束之前isAlive就是true 

    调用start之前,run结束之后isAlive就是false

(4)Thread一些重要的方法

one.start:决定了系统是否真正创建线程
two.中断线程:让线程停下来
    ->关键是让线程对应的run方法执行完(特殊的main线程,就是让main方法执行完,线程就完了)

    1)手动设置一个标志位,来控制这个线程是否要执行结束(可以在主线程中更改标志位的值来控制线程的执行)

    2)使用Thread中内置的一个标志位来进行判定

    可以通过

    Thread.interrupted()->静态方法

    Thread.currentThread().isInterrupted()这个方法判定的标志位是每个实例的标志位  
public class demo7 {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("test");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        });

        t.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       t.interrupt();
    }
}
    在main方法里先对main线程休眠5s,再t.interrupt();就是对t现成的标志位进行设置,这样就能影响到t线程中的标志位,进而控制线程结束。
three.线程等待,join
    线程之间调度顺序是不确定的,是按照调度器进行安排的,整个过程可以视为随机,但是我们想要控制线程之间执行的顺序,就可以使用到线程等待。

    join方法:哪个线程调用join,哪个线程就会阻塞等待,等到对应线程的run方法执行完毕
public class demo8 {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("test");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        });

        t.start();
        try {
            t.join(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }
}
    此时是main线程调用这个方法,针对t这个对象调用的,此时就是让mian线程等待t线程执行结束->t的run方法执行结束,调用join方法后,mian线程就进入阻塞状态(阻塞状态就是无法上CPU执行)

    通过线程等待,就是在控制让t先结束,main后结束,一定程度上控制了这两个线程的执行顺序

    join操作,在默认情况下就是死等->不合理->提供了另一个版本,就是可以执行等待时间

    join(10000)就是十秒内t完成就join直接返回,十秒之后main线程直接不等了
four.获取当前线程的引用
    Thread.currentThread();->哪个线程调用的curretThread()就返回哪个线程的实例

five.线程的休眠 sleep
    如果是一个进程有多个线程,此时每个线程都有一个PCB,一个进程就对应一组PCB了,PCB上有一个字段tgroupId,这个id相当于进程的id,同一个进程中的诺干个线程的tgroupId是相同的。

    如果某个线程调用了sleep方法,就会把线程从就绪队列拿到阻塞队列上。
标签: java

本文转载自: https://blog.csdn.net/HZH030126/article/details/140807276
版权归原作者 HZH030126 所有, 如有侵权,请联系我们删除。

“Java Web 01_day”的评论:

还没有评论