JavaWeb 基础知识(二)——线程01

发布时间:2022-07-02 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了JavaWeb 基础知识(二)——线程01脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

文章目录

  • JavaWeb 基础知识(二)多线程01
  • 一、认识线程
    • 0.线程的引入
    • 1.线程的概念
    • 2.进程与线程
    • 例子
  • 二、Java中的线程
  • 1.线程的创建
    • (1)run 和 start
    • (2)创建线程的几种方式
    • (3)jconsole 查看线程信息
    • (4)多线程的优势-增加运行速度
  • 2.Thread 的常见构造方法
  • 3.Thread 的几个常见属性
    • ID
    • 名称
    • 状态
    • 优先级
    • 后台线程
    • 存活
  • 未完待续...

JavaWeb 基础知识(二)多线程01

上节回顾

  我们在介绍本节内容之前,先来简单复习一下上一节进程的相关内容

JavaWeb 基础知识(二)——线程01

一、认识线程

0.线程的引入

  引进进程的目的,就是为了能够"并发编程"

  虽然多进程已经能够解决并发的问题了,但是我们认为,还不够理想。

创建进程、销毁进程、调度进程开销有点大了

进程时系统资源分配的基本单位

创建进程,就需要分配资源 销毁进程,就需要释放资源

  于是程序员就发明了一个 “线程”(Thread)的概念,线程在有些系统上也被叫做"轻量级进程".

轻量: 创建线程比创建进程更高效 创建线程比销毁线程更高效 调度线程比调度进程更高效

JavaWeb 基础知识(二)——线程01

1.线程的概念

一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行着多份代码.

我们站在系统内核的角度,再来看进程和线程

一个系统之中可能有很多个PCB(进程控制块),各个PCB通过链表进行连接,如图代表系统中已有的4个进程 ( pid 分别代表着进程id )

JavaWeb 基础知识(二)——线程01

在Linux系统中,线程同样是使用PCB来描述的

进程1,对应一个PCB,在这个进程1里创建一个线程,也是再加了一个PCB

JavaWeb 基础知识(二)——线程01

  这就是当前我们看到的一个情况,那其实,站在操作系统内核的角度,不分“线程”还是“进程”,系统只认PCB

  我们用户在创建一个进程出来,系统内核方面就会有一个PCB插入到双向链表里面,如果我们在代码中再去创建一个新的线程,也就是再加一个PCB

  像上面的 进程2、进程3、进程4,他们看起来没有创建其他线程,但是进程创建之初,也会有一个PCB产生,我们可以把PCB视作里面的一个线程

我们可以得到一个结论:

  当我们创建了一个进程的时候,就是创建了一个PCB 出来,同时这个PCB也可以是当做是目前进程已经包含了一个线程了,所以一个进程中至少有一个线程。

  同一个进程的线程之间,是可以共用一份内存空间的,同时其他的进程(PCB)使用的是独立的内存

  也就是说上面的进程中,进程1和线程1 共用一份内存空间,进程2、进程3、进程4都有自己独立的内存空间。

  这就是我们站在系统内核的角度描述线程基本的情况

那我们又拐回来了,线程和代码有啥关系呢?

可以认为,一个线程就是代码中的一个执行流

执行流:按照一定的顺序来执行一组指令

2.进程与线程

  进程与线程之间本来就是容易搞混淆的,尤其是对于Linux系统来说,进程和线程之间又是存在着千丝万缕的联系,总之呢,我们得知道 进程和线程之间 的区别和联系

经典面试题

进程和线程之间的区别和联系[面试题]

1、进程是包含线程的。一个进程里可以有一个线程,同时也可以有多个线程。

2、每个进程都有独立的内存空间(虚拟地址空间),同一个进程的多个线程之间,共用一个虚拟地址空间。

3、进程是操作系统分配资源的基本单位,线程是操作系统调度执行的基本单位

  上节课所介绍的"进程调度",当时咱们是没有考虑线程的,实际上系统是以线程(PCB)为单位进行调度执行的.

咱们来画图说明:

JavaWeb 基础知识(二)——线程01

3个进程4个线程

  我们先让CPU处理 第一个PCB块,执行一段时间之后,把PCB1 释放,再来执行PCB2,执行一段时间后,再进行释放,所以系统是根据PCB进行调度执行的.

  以上就是我们所讲的 线程和进程之间的区别与联系,上面的三点大家一定要有印象,是后面在面试的时候经常问到的问题。

例子

  刚才我们都一直在干巴巴的讲理论,可能是有点抽象了,那我们就再举一个例子进行说明一下吧(很形象)

主角:滑稽老铁 道具:封闭的房间与桌上的100只鸡

JavaWeb 基础知识(二)——线程01

现在呢,房间里的桌上有100只鸡,如何提高滑稽老铁吃鸡的速度?

那么此时,我们就有两种方案:

1、多进程

那么多进程是怎么吃呢?

多进程吃鸡

JavaWeb 基础知识(二)——线程01

现在有两个房间,两套桌子,把鸡平均分成两份,两个滑稽老铁同时再房间各自吃50只鸡

  这种分配的方法相比较于之前明显吃鸡的速度要高效好多。

  这就是我们所说的并发编程的效率,能够提升整体程序的效率

两个房间、两套桌子,说明每次再创建进程,都要给这个进程分配一些资源

这两个房间里的滑稽老铁,相互之间都看不见彼此,说明进程之间有独立的地址空间(进程的隔离性)

  这就是我们所说的多进程的吃鸡版本!

  当然了,两个房间、两套桌子总体来说成本还是有点高的,所以我们为了降低成本,那么我们还可以多线程吃鸡~

2、多线程

多线程吃鸡怎么吃?我们这样做~

还是一个房间,只不过多了一个滑稽老铁来一块吃鸡

JavaWeb 基础知识(二)——线程01

多了一个滑稽老铁(多了一个吃鸡的执行流)

  每个滑稽吃50只鸡就行了 ~ 1个人吃50只肯定比1个人吃100只速度更快一些

这里还有一个很重要的问题:

  这两个滑稽老铁共用了同一个房间和桌子,一个进程的多个线程之间,共用一个虚拟地址空间.同时,这两个滑稽老铁是可以看到对方的情况的

只创建了一个滑稽,桌子和房间都没有新创建,创建这个线程的成本比创建进程的成本要更低.

  这就是多线程吃鸡的一个情况,那么接下来呢?

  我们多线程吃鸡吃着吃着觉得效率还不够高,还可以进一步怎么提高效率呢?

进一步提高效率:再多搞几个滑稽(线程)

JavaWeb 基础知识(二)——线程01

  滑稽的数目(线程的数目)更多了,每个滑稽的任务就更少了,因此整体的效率就更高了~~

  就是说随着我们线程数目的增多,线程去完成同一个任务,我们的速度就会更快

  但是大家注意,这里的速度也不是说线程的数目越多越好!!如果线程的数目太多了,线程之间就会更加频繁的进行调度,调度的开销也就无法忽略了!!

就会出现下面的情况

JavaWeb 基础知识(二)——线程01

我们增加了滑稽(线程)的数目,就可能出现有的滑稽抢不上位置(CPU),于是任务的执行速度反而会变慢,这什么意思呢?

  没有抢着位置的三个老铁,为了吃鸡,要往里面挤,于是已经围着桌子吃起来的滑稽老铁们就没有办法消停的吃鸡了,有的滑稽就可能本来吃的好好的,没一会被挤出来了,这样就会出现很多问题~

  所以线程也不是越多越好,线程的数目越多,就会引发更多的调度开销,反而可能让执行任务速度变得更慢~所以这一点呢,大家也要明确.

  还有一种情况,当我们很多滑稽老铁一起吃鸡的时候,可能有打架的行为~~

什么叫打架的情况呢?

还是刚才的饭局

JavaWeb 基础知识(二)——线程01

两个滑稽(线程)同时看上一个鸡大腿(准备修改同一块内存的数据),这个时候就会起冲突.

  这种情况,我们称为"线程不安全",这同样也是多线程编程的重点问题,在后面的章节会着重介绍!!

还有一种情况,如果某个滑稽老铁不开心,某个滑稽(线程)一直抢不到桌子的位置

JavaWeb 基础知识(二)——线程01

于是这个老铁一生气,把桌子给掀了!!

这说明什么呢?

一个进程里面如果某个线程抛出了异常,并且没有合理catch住的话,就可能导致整个进程都异常退出.其他线程也就玩完了

  所以一个线程不工作,其他的线程也全都不工作了,这一点,就对我们的多线程程序的安全性提了更高的呃要求

  多线程程序的编写,其实就提出了一个更高的要求,一定要保证线程的稳定

讲到这呢,那么我们滑稽的案例就告一段落了~ 希望通过这样的一个例子,让大家更好的了解进程与线程之间的关联关系

  以上就是我们所介绍的进程与线程的关联与特点,准确的来说是线程的一些特点,这些特点我们在以后写代码的时候也就会逐步的感受到了~好了,说了那么多,都是理论的知识,理论的知识大家有一个简单的认识就可以了,重点我们还是要落在代码上!!

  那么接下来,我们就介绍 使用Java来操作线程Thread类(创建线程)的相关方法

二、Java中的线程

  在Java当中,是使用Thread这个类的对象来表示一个操作系统中的线程

PCB是在操作系统内核中,描述线程的 而Thread类则是在Java的代码中 描述线程的.

接下来,我们就来写一下简单的代码来创建线程出来~

1.线程的创建

  首先我们得去创建Thread 的实例出来,但是常见的方式并不是直接new一个对象出来.

  Thread 是Java标准库中描述的一个关于线程的类.

  常见的方式就是自己定义一个类继承Thread,然后重写Thread中的 run 方法,run 方法就表示线程要执行的具体任务(代码)

JavaWeb 基础知识(二)——线程01

start 方法,会在操作系统中真的创建一个线程出来(同时在内核中会创建PCB,加入到双向链表当中)

执行一下

JavaWeb 基础知识(二)——线程01

这个新的线程,就会执行 run中所描述的代码

  看完这个线程的创建过程,有的同学不禁会问了,

  我们在 执行代码的时候 不用 t.start ,直接执行 t.run 行不行?咱们刚才不是把代码的逻辑定义到run方法里面了嘛,那我们直接调用t.run 不是一样会执行代码嘛??

执行一下

JavaWeb 基础知识(二)——线程01

那么run 和 start 方法有什么区别呢?

(1)run 和 start

重点:经典面试题

run 和 start 的区别是非常非常大的,我们来给大家具体演示一下这个情况.

start 方法

当我们运行Java代码的时候,首先系统会创建一个进程,这个进程里面已经包含了一个线程了,这个线程执行的代码默认就是 main 方法 ,main方法调用t.start方法,在系统中又会创建一个线程(PCB)出来,然后这个PCB执行任务代码.

JavaWeb 基础知识(二)——线程01

run 方法

run 方法没有创建新的PCB,没有创建新的线程.

JavaWeb 基础知识(二)——线程01

t.run 这里并没有创建出一个新的线程,

而使用t.start 这个方法可以创建出新的线程,同时t.start 的两个线程之间是属于同一进程,属于并发的关系

例子

如果大家还是没有懂的话,给大家再举一个例子:

比如说老王想买一瓶酱油,start 方法就是 老王把儿子小王叫来说,你去楼下超市去买一瓶酱油,run方法就是 老王自己去买一瓶酱油。

这两种方式的区别,尤其是start,就是在派小王去买酱油的同时,老王自己同时想干嘛就干嘛,这就是并发的效果.而run方法只能老王只能去买酱油了,没法干其他别的事

这样的一个区别大家一定要区分请

让大家看一下程序并发的效果

JavaWeb 基础知识(二)——线程01

  在上面的代码中,Mythread 中执行的是一个死循环,他会一直循环执行,在主线程Main里也会一直执行循环,那么都是死循环,这两个代码能同时执行吗?

  按照上面的讲解,MyThread 是一个执行流,Main 也是一个执行流,他们属于并发的关系,所以可以同时执行!

那么运行程序,观察结果~

JavaWeb 基础知识(二)——线程01

  “hello main” 和 “hello thread” 进行交替打印,进一步验证了两个线程并发执行的效果。

  通过刚才这个代码,我们就可以看到,我们通过线程可以让两个死循环按照并发执行的方式,一起来执行,而不是单纯的说,一个执行完才去执行另外一个。好了,这是我们通过 start 创建线程来这样做的,如果我们改成 run呢?

JavaWeb 基础知识(二)——线程01

执行代码,观察结果

JavaWeb 基础知识(二)——线程01

  就会在MyThread 的死循环中转不出去,main的循环无法执行

  这里我们也可以看到 start 和 run之间很本质的区别,run 并没有创建出新的线程,它属于一个线程里面串行执行,而通过start 就可以创建新的线程,可以是两个线程以并发的方式同时来执行.

以上就是关于线程最基本的代码~

(2)创建线程的几种方式

  在上面的程序中,我们是通过新建一个类继承标准库中的Thread类来创建线程的,实际上,线程的创建是有很多种方式的,下面我们就来了解一下 Java当中创建线程的几种方式.

1.继承Thread,重写run

  我们自己建一个类继承Thread ,在这个类中重写run方法.

**加粗样式**

    class Mythread1 extends Thread{
        @Override
        public void run() {
            // 执行的任务
        }
    }

2.实现Runable接口,重写 run

Runable 是标准库提供的一个接口,这个接口主要用于描述"一个任务",里面也是有一个核心的run方法,通过run方法来描述具体要执行的任务代码是什么…

JavaWeb 基础知识(二)——线程01


class MyRunable implements Runnable{
    @Override
    public void run() {
        //执行的任务
        while(true){
            System.out.println("hello world");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Thread1 {

    public static void main(String[] args) {
           Thread t1 = new Thread(new MyRunable());
           t1.start();
    }
}

注意:   我们的Runable 并不是能够独立去使用,还要搭配我们的 Thread 类来进行使用,new MyRunable 的实例作为 Thread 的参数,这就是当前这种方式的写法.

本质上和刚才继承Thread重写Run的效果一样,都是具体告诉线程具体要执行的任务是什么…

只不过MyRunable只是用来描述一个具体的任务是什么,而真正线程的主体还是在于我们的Thread类本身.

  同时这种方式,还可以给当前创建的线程赋予名字,名字作为Thread 的第二个参数

JavaWeb 基础知识(二)——线程01

3.继承Thread,重写run(匿名内部类)

内部类:在一个类里面定义类

所以匿名内部类就是一个没有类名的内部类,没有类名也没有关系,至少可以创建出一个实例来~

那么我们什么时候需要用到匿名内部类呢? 只需要这个实例,不需要用到其他实例了, 匿名内部类的方式写起来更加简洁一些

那么下面我们具体来看具体的代码应该怎么写…

   public static void main(String[] args) {
      Thread t = new Thread(){
          @Override
          public void run() {
              // 执行任务代码
          }
      };
    }

  创建了一个匿名的类,这个类继承了 Thread,此处new 的实例其实是Thread类的子类的实例

4.实现Runnable 重写run,使用匿名内部类

  因为都是用的匿名内部类,所以这两种写法都很像,但是我们仔细观察会发现,写法不太相同.

JavaWeb 基础知识(二)——线程01

JavaWeb 基础知识(二)——线程01

我们把两组代码拿过来对比一下,可以看出区别来

第一种:我们创建匿名内部类是Thread 的子类,所以 {} 跟在 Thread 的后面

第二种:我们创建的是一个Runnable 这样的子类,new的 Runnable 子类作为 Thread 的一个参数。相当于创建了一个匿名内部类的实例,把这个实例作为 Thread的参数。

这两种写法还是有一定区别的.

  以上的这些创建线程的方式,本质上都相同

  只不过区别是,指定线程要执行的任务的方式不一样,此处的区别,其实都是单纯的Java语法层面的区别~ 所以这样的区别并不是很关键,这样的写法大家只需要多写两次去熟练就会了…

好了,写到这里,可能有同学说了

我们已经了解了创建线程的几种方式了,也知道如何并发执行了,那么有没有像任务管理器一样的东西让我们能够看到 Java创建的线程呢?

(3)jconsole 查看线程信息

  在 JDK 中内置了一个 jconsole 工具,就可以看到线程的信息.

我们先在Java运行一个线程

JavaWeb 基础知识(二)——线程01

点击运行,看一看jconsole 里面的线程信息

jconsole 在哪里找呢? 先找到我们的jdk文件,bin目录下就有 jconsole.exe

打开jconsole 之后出现这样的界面

JavaWeb 基础知识(二)——线程01

选择本地进程Main,然后点击连接

注意在这里,显示的进程只是Java相关的进程,非Java的进程显示不出来

JavaWeb 基础知识(二)——线程01

  这些线程都是当前进程的线程,对于一个Java程序来说,启动的时候不仅启动了main这样一个线程(main这个线程是 main方法对应的线程, thread-0 这个就是我们自己创建的新的线程),还有很多其他的线程,这些线程都是JVM在运行的时候内置的一些线程…

我们可以通过 这个工具查看每个线程的具体情况

JavaWeb 基础知识(二)——线程01

如果写的程序,发现程序挂了,就可以通过 jconsole 来查看程序里面每个线程的情况,对于分析解决问题就有很大帮助了

  以上就是用 jconsole 来查看 线程相关信息的具体操作,当然了我们还可以根据其他的信息来查看,我们就暂时不去介绍这么多了~

  好了,我们继续线程的另一块知识~

(4)多线程的优势-增加运行速度

  之前我们介绍并发编程能够提高程序的效率,我们呢就通过 Java 的代码来了解一下 并发编程的效率

这个代码我们要干什么呢?

首先我们有一个很大的数字,这个数字是10亿

首先是串行执行代码,a、b分别自增10亿次

JavaWeb 基础知识(二)——线程01

我们来看一下执行结果:

JavaWeb 基础知识(二)——线程01

然后是并发执行,让a、b分别在两个线程中并发执行自增操作,然后计时.

JavaWeb 基础知识(二)——线程01

运行查看结果:

JavaWeb 基础知识(二)——线程01

当前呢,使用并发的方式 确实比 串行的方式时间上 效率提高很多,

串行执行 600—700 ms 并发执行 300—400 ms 速度确实提高了好多

速度提高正好是提高一倍嘛? 不是~(不一定) 主要是因为线程调度自身也是有开销的~

串行执行: 一个线程执行了20亿次循环,中间可能调度若干次

并发执行:两个线程各自执行10亿次循环,中间可能调度若干次.

因为系统有调度,所以会对程序的运行时间有影响

2.Thread 的常见构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
【了解】Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可

具体代码使用:

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

3.Thread 的几个常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否是后台线程isDaemon()
是否存活isAlive
是否被中断isInterrupted

ID

ID 是线程的唯一标识,不同线程不会重复

名称

名称是各种调试工具会用到

状态

状态表示线程当前所处的一个情况,和上一节说的"进程的状态"是类似的效果,存在的意义都是辅助进行线程调度

优先级

优先级高的线程理论上来说更容易被调度到,和上节课"进程的优先级"是类似的效果

  此处的状态和优先级 ,和PCB中的状态优先级并不完全一致,Java线程在这个基础上有做了自己的丰富.

后台线程

关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。

我们在Java中创建的线程一般默认都是非后台线程,此时,如果main方法结束了,线程还没结束,JVM不会结束

如果当前线程是后台线程,此时如果main方法结束了,线程还没结束,那么JVM进程会直接结束,同时也把这个后台线程给带走了.

存活

是否存活,即简单的理解,为 run 方法是否运行结束了

JavaWeb 基础知识(二)——线程01

存活是什么意思呢》我们来画一下

JavaWeb 基础知识(二)——线程01

t中的代码执行完之后,Java中的线程PCB也会同时销毁吗?并不会.

Java 中PCB对象在JVM 垃圾回收机制下才会被销毁,而操作系统内核的 PCB 在代码执行完之后就销毁了

所以我们就可以通过 isAlive() 判断内核中的PCB是否存在

我们对当前程序中的线程查看属性


class MyRunable implements Runnable{
    @Override
    public void run() {
        //执行的任务
        while (true){
            System.out.println("hello wolrd");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Thread1 {
    public static void main(String[] args) {
           Thread t1 = new Thread(new MyRunable(),"线程01");
           t1.run();
        System.out.println("id: "+t1.getId());
        System.out.println("name: "+t1.getName());
        System.out.println("Priority: " +t1.getPriority());
        System.out.println("State: "+t1.getState());
        System.out.println("isAlive: "+t1.isAlive());
        System.out.println("isDeamon: "+t1.isDaemon());
    }
}

JavaWeb 基础知识(二)——线程01

JavaWeb 基础知识(二)——线程01

  好了,今天的线程就讲到这里,希望大家多多复习~

谢谢欣赏!!

下一篇 JavaWeb基础知识(三)——线程02 敬请期待~

未完待续…

脚本宝典总结

以上是脚本宝典为你收集整理的JavaWeb 基础知识(二)——线程01全部内容,希望文章能够帮你解决JavaWeb 基础知识(二)——线程01所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。
标签:并发