多线程基础 保姆级讲解

认识线程

1)线程是什么

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

2) 为啥要有线程

A.首先, “并发编程” 成为 “刚需”. 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU.
而并发编程能更充分利用多核 CPU 资源.

B.有些任务场景需要 “等待 IO”, 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.

C.其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量.
创建一个进程,消耗的时间比较多 消耗在资源分配上,进程是资源分配的基本单位 分配内存是一个大活 操作系统内部有一个数据结构,把空闲的内存分块整理好。当我们进行申请内存空间的时候,操作系统就会从这样的数据结构中,找到一个合适大小的空闲内存返回给对应的进程
虽然这里的数据结构可以一定程度上提高效率,单整体来说,管理的空间比较多,相比之下还是一个耗时的操作
销毁一个进程,消耗的时间比较多
调度一个进程,消耗的时间比较多 相比之下
创建线程比创建进程更快.
销毁线程比销毁进程更快.
调度线程比调度进程更快.

最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 “线程池”(ThreadPool) 和 “协程”(Coroutine)
(关于线程池我们后面再介绍. 关于协程的话题我们此处暂时不做过多讨论. )

线程不能独立存在,而是要依附于进程。 一个进程可以包含一个或者多个线程。
一个进程最开始的时候,至少有一个线程。这个线程负责完成执行代码的工作。也可以根据需要创建出更多的线程,从而使当前实现 并发编程的效果

并发编程
并行和并发统称为并发 并行:多个线程同时在多个CPU上执行 并发:进程太多,CPU核心数太少。就需要多个进程在同一个CPU核心上轮番执行。只要**轮转的速度足够快,**看起来就是这些进程在
*同时执行

总结:进程包含线程 =》一个线程对应一个CPB =》 一个进程包含一个或者多个线程 =》 每个线程都有自己的状态,上下文,优先级,记账信息 =》 每个线程都可以独立的去CPU上调度执行 =》 同一个进程的CPB共用了同样的内存指针和文件描述符表 =》创建线程(PCB)不需要重新申请资源 =》线程创建和销毁的效率都更高

3) 进程和线程的区别 (经典面试题)

1.进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
2.进程和进程之间不共享资源. 同一个进程的线程之间共享同一个资源(内存资源和硬盘资源),
3.进程是系统分配资源的最小单位,线程是系统调度的最小单位。
4.进程和线程都是用来 实现并发编程 场景的,但是线程比进程更轻量,更高效
5.进程和进程具有独立性,一个进程挂了,不会影响其他进程。但是同一个进程的线程之间,是可能相互影响的(线程安全问题和线程出现异常,后续会详细介绍)

多线程

线程的创建方式

方法1 继承 Thread 类
  1. 继承 Thread 来创建一个线程类.
  2. 创建 MyThread 类的实例
  3. 调用 start 方法启动线程
package thread;

class MyThread01 extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("Mythrend01");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Demo923 {
    public static void main(String[] args) {
MyThread01 t = new MyThread01();
t.start();
    }
}

方法2 实现 Runnable 接口
  1. 实现 Runnable 接口
  2. 创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
  3. 调用 start 方法
package thread;

class MyRunnable01 implements Runnable{
    @Override
    public void run() {
        System.out.println("MyRunnable01");
    }
}

public class Demo923 {
    public static void main(String[] args) {
MyRunnable01 myRunnable01 = new MyRunnable01();
Thread t2 = new Thread(myRunnable01);
t2.start();
    }
}


提示: Runnable是表示一个 可运行的任务,这个任务是交给线程执行还是交给其他实体执行,Runnable本身并不关心
Runnable的写法和直接继承Threa之间的区别,是解耦合 run()和start()方法的区别 run()方法描述了要执行什么样的任务 而 start()方法,则是调用系统API,在操作系统内创建出线程。

方法3 使用匿名内部类创建 Thread 子类对象
package thread;



public class Demo923 {
    public static void main(String[] args) {
	Thread t1 = new Thread(){
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Thread 子类对象");
    }
};
t1.start();
    }
}

方法4 匿名内部类创建 Runnable 子类对象
package thread;
public class Demo923 {
    public static void main(String[] args) {
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Runnable 子类对象");
    }
};
Thread t1 = new Thread(r1);
t1.start();
    }
}

方法5 lambda 表达式创建 Thread 子类对象
package thread;
public class Demo923 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("使用匿名类创建 Thread 子类对象");
        });

        t1.start();

    }
}

(还有一些创建线程的方式后面会详细介绍哦)

Thread类及常见方法

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关
联。
每个执行流,也需要有一个对象来描述,类似下图所示,(如同每个人都要有身份证标识身份)而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
thread对象

Thread 的常见构造方法

Thread() 创建线程对象 Thread(Runnable target) 使用 Runnable 对象创建线程对象

Thread(String name) 建线程对象,并命名

Thread(Runnable target, String name) 使用Runnable 对象创建线程对象,并命名

【了解】Thread(ThreadGroup group,Runnable target) 线程可以被用来分组管理,分好的组即为线程组,这
个目前我们了解即可

Thread 的几个常见属性

hread 的几个常见属性

getId()

ID是线程的唯一标识,不同线程不会重复。(这里的id是java给你这个线程分配的,不是系统API提供的线程,更不是PBC的id)

getName()

创建线程的时候可以去指定name不影响线程的执行,只是给线程起个名字,后续在调试的时候,比较方便区分
名称是各种调试工具用到的 比如:使用jconsole(JDK中自带的工具)来观察进程里的多线程情况 jconsole.exe在JDK安装目录的bin目录里面

jconsole
jconsole

getState()

getState() 获取线程的状态,状态表示线程当前所处的一个情况,下面我们会进一步说明

isDeamon()

isDeamon() 判断线程是否为守护线程
守护线程(后台线程)相比之下,后台线程,不结束,不影响整个进程的结束。
默认情况下,一个线程是前台线程
如果前台线程,没有执行结束,此时整个进程是不会结束的

举个例子

public class Demo923 {
    public static void main(String[] args) {
       Thread t = new Thread(()->{
           while (true){
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       },"这是新线程");
       //t.setDaemon(true);
       t.start();


    }
}

在这里插入图片描述

虽然主线程飞快执行完了,但是 t 这个前台线程没执行完,所以进程不结束

package thread;
public class Demo923 {
    public static void main(String[] args) {
       Thread t = new Thread(()->{
           while (true){
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       },"这是新线程");
       t.setDaemon(true);
       t.start();


    }
}

在这里插入图片描述

此时把t改为后台线程之后,主线程飞快执行完了,此时也没有其他前台进程了,于是进程就结束了。t来不及执行就结束了。

isAlive()

isAlive()判定内核线程是不是已经没了

public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("线程开始");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程结束");
        });
        t.start();
        System.out.println(t.isAlive());
        try {
            Thread.sleep(3000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(t.isAlive());
    }

在这里插入图片描述

ture线程开始这两个日志谁先打印不一定,线程是并发执行的,并发调度的顺序不确定,取决于系统的调度器(如果大家尝试,大概率先打印true,因为调用start之后,新的线程被创建也是有一定开销的,创建过程中,主线程就执行了println语句。当然也无法排除极端情况,主线程正好卡了一下,是新线程的println语句先打印)

isInterrupted()

isInterrupt()判断一个线程是否被终止

`如何中断一个线程 如何让一个线程停止运行 在java中,要终止/销毁一个线程,做法比较唯一,就是想办法让run()方法尽快执行结束

可以在代码中手动设置标志位,来作为run()的执行结束的条件

public class Demo923 {
    private static boolean isQuit = false;
    public static void main(String[] args) {
        Thread t = new Thread(()->{
           while (!isQuit){
               System.out.println("线程工作中");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }

        });
        t.start();
    }
}

当前的这个代码,是使用一个isQuit变量作为标志位,可以通过修改isQuit的值让run()尽快结束。

上述方法不够优雅:

1.需要手动创建变量
2.当线程内部在sleep的时候,主线程修改变量,新线程不能及时响应 因此就需要使用另外的方式来完成上述线程中断操作

(今天先讲到这里,中断方法下一篇做详细解释哦~~)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值