【JavaEE初阶】多线程(一)认识线程 线程的创建 Thread的用法

摄影分享!
在这里插入图片描述
在这里插入图片描述

认识线程(Thread)

概念

引入“进程”这个概念,主要是为了解决“并发编程”的问题。
其实,多进程编程,已经可以解决并发编程的问题了。但是由于进程太重(消耗资源多,速度慢)。创建,销毁,调度一个进程的开销都比较大。主要是重在了“资源分配/回收”上。所以线程应运而生。线程也叫做“轻量级进程”解决并发编程问题的前提下,让创建,销毁,调度的速度更快一些。线程通过把申请资源/释放资源的操作省去所以线程更

举个例子~
在这里插入图片描述
在以上方案中,显然,方案2比方案1的成本要小很多。场地和物流线都可以使用之前的。这就是多线程版本的方案。此时,只是搞第一套生产线的时候,需要把资源申请到位,后续再加新的生产线,此时就复用之前的资源即可。
**线程和进程之间的关系,是进程包含线程。**一个进程可以包含一个线程,也可以包含多个线程(不能没有)。只有第一个线程启动的时候,开销是比较大的,后续的线程开销就小了。同一个进程里的多个线程之间,公用了进程的同一份资源。(主要指的是内存和文件描述符表)
如果每个进程由多个线程了,每个线程是独立在CPU上调度的。(线程是操作系统执行的基本单位)
每个线程也有自己的执行逻辑(执行流)
在这里插入图片描述
一个线程也是通过一个PCB来描述的。一个进程里可能是对应一个PCB,也可能是对应多个PCB。在同一个进程里的PCB之间,PID是一样的。内存指针和文件描述符表也是一样的。

再举个例子~

在这里插入图片描述
什么时候会出现这种安全问题呢?
多个执行流访问同一个共享资源的时候。
线程模型,天然就是资源共享的,多线程争抢同一个资源(同一个变量)非常容易触发的。
进程模型,天然是资源隔离的,不容易触发,进行进程间通信的时候,多个进程访问同一个资源,就可能会出问题。
多线程会提高效率,但不如多进程安全。

执行多线程编程

本身关于线程的操作,操作系统提供API。
Java是一个跨平台的语言,很多操作系统的提供的功能,都被JVM封装好了。由于Java在不同的操作系统上,实现了不同版本的JVM,所以Java实现了跨平台。
Java操作多线程,最核心的类是Thread。

class MyTread extends Thread{
    
    
    @Override
    public void run() {
    
    
        System.out.println("hello run");
    }
}
public class TreadDemo {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new MyTread();
        t.start();
    }
}

在这里插入图片描述

t.start();

中start创建了一个新的线程。start中没有调用run。而是创建了一个线程,由线程来执行run方法。
就是调用操作系统的API,通过操作系统内核创建新线程的PCB,并且要把执行的指令交给当前PBC。当PCB被调度到CPU上执行的时候,也就执行到了线程run方法中的代码了。
java有一个主线程main,通过t.start();主线程调用t.start创建出一个新的线程,新的线程调用run
run方法执行完毕,新的这个线程自然销毁。

import static java.lang.Thread.sleep;

class MyTread extends Thread{
    
    
    @Override
    public void run() {
    
    
        while(true) {
    
    
            try {
    
    
                sleep(1000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
            System.out.println("hello run");
        }
    }
}
public class TreadDemo {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new MyTread();
        t.start();
        while(true){
    
    
            try {
    
    
                sleep(1000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
            System.out.println("hello main");
        }
    }
}

在这里插入图片描述
start和run之间的区别:
start是真正创建了一个线程,线程是独立的执行流。run只是描述了线程要做什么。
可以使用jdk自带的工具jconsole查看当前的java进程中的所有线程。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
PCB不是“简称”是一个数据结构,体现的是进程/线程是如何实现,如何被描述的。

在这里插入图片描述
堆栈跟踪(调用栈):描述了当前方法的调用关系。

创建线程的写法

1.继承Thread,重写run

在这里插入图片描述

2.实现Runnable接口

class MyRunnable implements Runnable{
    
    
    @Override
    public void run() {
    
    
        System.out.println("hello thread");
    }
}
public class TreadDemo2 {
    
    
    public static void main(String[] args) {
    
    
        //描述了一个任务
        Runnable runnable = new MyRunnable();
        //把任务交给线程来执行
        Thread t = new Thread(runnable);
        t.start();
    }
}

在这里插入图片描述

3.使用匿名内部类,继承Thread

public class ThreadDemo3 {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new Thread() {
    
    
            public void run(){
    
    
                System.out.println("hello");
            }
        };
        t.start();
    }
}

在这里插入图片描述

4.使用匿名内部类,实现Runable

public class ThreadDemo4 {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("hello");
            }
        });
        t.start();
    }
}

在这里插入图片描述

5.使用Lambda表达式

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

在这里插入图片描述
在这里插入图片描述

Thread用法

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

在这里插入图片描述
t为代码里的变量名,mythread是系统里的线程名。

Thread的几个常见属性

属性 获取方法
ID getId()
名称(构造方法中起的) getName()
线程状态 getState()
优先级(可以设置,但没用) getPriority()
是否后台(守护线程)线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()

说明:

isDaemon()
前台线程:会阻止进程结束,前台线程的工作没做完,进程无法结束。 后台线程:不会阻止进程结束,后台线程工作未完成,进程可以结束。
代码中手动创建的线程,默认都是前台的。main线程默认也是前台的。 可以手动使用setDaemon设置成后台线程。
在这里插入图片描述

isAlive()
在这里插入图片描述

在这里插入图片描述
如果t的run还没跑,isAlive就是false
如果t的run正在跑,isAlive就是true
如果t的run跑完了,isAlive就是false

在这里插入图片描述
运行结果:
在这里插入图片描述
两个线程在微观上可能是并行的(两个核心),也可能是并发的(一个核心)宏观上无法感知,我们所看到的就是随机执行。系统是**抢占式调度。**多线程代码,需要考虑很多种可能性!

启动一个线程-start()

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。

覆写 run 方法是提供给线程要做的事情的指令清单
线程对象可以认为是把 李四、王五叫过来了
而调用 start() 方法,就是喊一声:”行动起来!“,线程才真正独立去执行了。

调用start方法,才是真的在操作系统的底层创建出一个线程。

线程终止

线程终止,不是让线程立即停止。而是通知线程要停止了。但是至于线程是否停止了,取决于代码的具体写法。

举个列子: 我在打游戏,麻麻突然告诉我家里的酱油没了。让我去打酱油~我有以下几个选择:

  1. 停止游戏,立即去
  2. 打完这把,再去
  3. 假装没听见,不去

类比如下:

  1. 使用标志位来控制线程是否要停止。
    在这里插入图片描述
  2. 使用Thread自带的标志位来进行判断
public class ThreadDemo8 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t = new Thread(()->{
    
    
            while (!Thread.currentThread().isInterrupted()){
    
    
                System.out.println("hello thread");
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        t.interrupt();
    }
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
interrupt会做两件事:

  1. 把线程内部的标志位(boolean)设置成true。
  2. 如果线程正在进行sleep,就会触发异常。把sleep唤醒~

sleep被唤醒后,会将刚才设置的标志位,再设置回false。

t线程忽略终止请求:
在这里插入图片描述

线程t立即响应终止请求:
在这里插入图片描述
稍后进行终止:
在这里插入图片描述

等待一个线程join()

线程是一个随机调度的过程。等待线程就是在控制两个线程的结束顺序。

在这里插入图片描述
本身执行完start之后,t和main线程就并发执行。main继续往下执行,t也会继续往下执行。但是此时使用了join,就导致main线程发生阻塞(block)。一直阻塞到t线程执行结束,main线程才会从join中恢复,才能继续往下执行。
t线程肯定比main线程先结束。
在这里插入图片描述
在这里插入图片描述

方法 说明
public void join() 等待线程结束
public void join(long millis) 等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos) 同理,但可以更高精度

获取当前线程的引用

方法 说明
public static Thread currentThread(); 返回当前线程对象的引用

休眠当前线程

方法 说明
public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throws InterruptedException 可以更高精度的休眠

操作系统内核中:
就绪队列链表中的PCB都是“随叫随到”的就绪状态。
在这里插入图片描述

操作系统每次需要调度一个线程去执行,就从就绪队列中选择一个。
线程A调用sleep,A就会进入休眠状态,把A从上述链表中拿出来,放入另一个链表中(阻塞队列)。

阻塞队列链表中的PCB都是“阻塞状态”,暂时不参与CPU的调度执行。
在这里插入图片描述
一旦线程进入阻塞状态,对应PCB就进入阻塞队列了,此时就暂时无法参与调度了。

猜你喜欢

转载自blog.csdn.net/qq_61138087/article/details/130136985