ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

JAVA并发一(Java线程)

2021-12-12 17:03:49  阅读:130  来源: 互联网

标签:Java Thread t1 线程 打断 JAVA 方法 sleep


1.并发和并行的区别

并发是一个CPU在不同的时间去不同线程中执行指令。

并行是多个CPU同时处理不同的线程。

引用 Rob Pike 的一段描述:
        并发(concurrent)是同一时间应对(dealing with)多件事情的能力
        并行(parallel)是同一时间动手做(doing)多件事情的能力

2.进程与线程

进程:

        程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。

        当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
        进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器、word等),也有的程序只能启动一个实例进程(例如网易云音乐、一些游戏进程等)

线程:

        一个进程之内可以分为一到多个线程。
        一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行 。
        Java 中,线程作为小调度单位,进程作为资源分配的小单位。 在 windows 中进程是不活动的,只是作为线程的容器。

        比如你可以在音乐软件听歌的同时进行搜索功能,这就需要至少两个线程完成这两个工作。

二者对比

        进程基本上相互独立的,而线程存在于进程内,是进程的一个子集 进程拥有共享的资源,如内存空间等,供其内部的线程共享。
        进程间通信较为复杂 同一台计算机的进程通信称为 IPC(Inter-process communication)
不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
        线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量。

        线程更轻量,线程上下文切换成本一般上要比进程上下文切换低。

3.Java线程

3.1 创建和运行线程

        方法一,直接使用 Thread;

    public void creat1(){
        Thread t1 = new Thread(){
            @Override
            public void run(){
                System.out.println("线程1启动了");
            }
        };
        t1.start();
    }

 lamda表达式写法:

        Thread t2 = new Thread(() -> 
                System.out.println("线程2启动了")
        );
        t2.start();

        方法二,实现runnable接口,使用 Runnable 配合 Thread:

    public void creat2(){
        Runnable task1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("线程3启动了");
            }
        };
        Thread t3 = new Thread(task1);
        t3.start();
    }

小结
        方法1 是把线程和任务合并在了一起
        方法2 是把线程和任务分开了,用 Runnable 更容易与线程池等高级 API 配合 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活。

        方法三,FutureTask 配合 Thread;

        FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况;Runnable的run方法没有返回值。

        

    public void creat3() throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(new Callable<>() {
            @Override
            public Integer call() {
                System.out.println("带返回结果的线程t4开始执行了");
                return 1;
            }
        });
        Thread t4 = new Thread(futureTask);
        t4.start();
        System.out.println("线程t4运行结束,结果为"+futureTask.get());
    }

        Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

总结
        使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递。

        而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制。

        前两种方式都没办法拿到任务的返回结果,但是Futuretask方式可以。

3.2 线程运行原理

        虚拟机栈与栈帧

        栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,是属于线程的私有的。当java中使用多线程时,每个线程都会维护它自己的栈帧!每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法;

        线程上下文切换(Thread Context Switch)

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完(每个线程轮流执行,看前面并行的概念)

  • 垃圾回收

  • 有更高优先级的线程需要运行

  • 线程自己调用了 sleepyieldwaitjoinparksynchronizedlock 等方法

当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念 就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的。

3.3 常用方法

 3.3.1 start 和run 方法:

        被创建的Thread对象直接调用重写的run方法时, run方法是在主线程中被执行的,而不是在我们所创建的线程中执行。所以如果想要在所创建的线程中执行run方法,需要使用Thread对象的start方法。

3.3.2 sleep()方法:

        在介绍sleep之前,首先介绍一下线程的状态:

        操作系统层面,线程的五种状态:

 

  1. 初始状态,仅仅是在语言层面上创建了线程对象,即Thead thread = new Thead();,还未与操作系统线程关联

  2. 可运行状态,也称就绪状态,指该线程已经被创建,与操作系统相关联,等待cpu给它分配时间片就可运行

  3. 运行状态,指线程获取了CPU时间片,正在运行

    1. 当CPU时间片用完,线程会转换至【可运行状态】,等待 CPU再次分配时间片,会导致我们前面讲到的上下文切换

  4. 阻塞状态

    1. 如果调用了阻塞API,如BIO读写文件,那么线程实际上不会用到CPU,不会分配CPU时间片,会导致上下文切换,进入【阻塞状态】

    2. 等待BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】

    3. 与【可运行状态】的区别是,只要操作系统一直不唤醒线程,调度器就一直不会考虑调度它们,CPU就一直不会分配时间片

  5. 终止状态,表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

        线程状态之六种状态:

这是从 Java API 层面来描述的,我们主要研究的就是这种。

 

  1. NEW 跟五种状态里的初始状态是一个意思

  2. RUNNABLE 是当调用了 start() 方法之后的状态,注意,Java API 层面的 RUNNABLE 状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【io阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)

  3. BLOCKED WAITING TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分。

基本状态介绍完了,然后可以重新介绍sleep状态了 :

sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)

  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,那么被打断的线程这时就会抛出 InterruptedException异常【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】

  3. 睡眠结束后的线程未必会立刻得到执行(需要分配到cpu时间片)

  4. 建议用 TimeUnit 的 sleep() 代替 Thread 的 sleep()来获得更好的可读性:

    //休眠一秒
    TimeUnit.SECONDS.sleep(1);
    //休眠一分钟
    TimeUnit.MINUTES.sleep(1);

 3.3.3 yield()方法:

        让出当前线程。

        1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态(仍然有可能继续被执行),然后调度执行其它线程;
        2. 具体的实现依赖于操作系统的任务调度器。

        yield使cpu调用其它线程,但是cpu可能会再分配时间片给该线程;而sleep需要等过了休眠时间之后才有可能被分配cpu时间片

3.3.4 join()方法:

        用于等待某个线程结束。哪个线程调用join()方法,就等待哪个线程结束,然后再去执行其他线程。如在主线程中调用ti.join(),则是主线程等待t1线程结束;

    private static void test1() throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            sleep(1);
            log.debug("结束");
            r = 10;
        },"t1");
        t1.start();
        t1.join();
        log.debug("结果为:{}", r);
        log.debug("结束");
    }

 

 流程图:

main函数开始,运行到t1.start,然后阻塞到t1.join等待t1运行结束,

3.3.5 interrupt 方法

用于打断阻塞(sleep wait join…)的线程。 处于阻塞状态的线程,CPU不会给其分配时间片。

sleep,wait,join 的线程,这几个方法都会让线程进入阻塞状态:

        如果一个线程在在运行中被打断,线程并不会暂停,只是调用方法,打断标记会被置为true。
        如果是打断因sleep wait join方法而被阻塞的线程,会将打断标记置为false

//用于查看打断标记,返回值被boolean类型
t1.isInterrupted();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while(true) {
                boolean interrupted = Thread.currentThread().isInterrupted();
                if(interrupted) {
                    System.out.println("被打断了, 退出循环");
                    break;
                }
            }
        }, "t1");
        t1.start();
        Thread.sleep(1000);
        System.out.println("interrupt");
        t1.interrupt();
    }

interrupt方法的应用——两阶段终止模式:

        当我们在执行线程一时,想要终止线程二,这是就需要使用interrupt方法来优雅的停止线程二。、

        如下所示:因为线程的isInterrupted()方法可以取得线程的打断标记,

        如果线程在睡眠sleep期间被打断,打断标记是不会变的,为false,但是sleep期间被打断会抛出异常,我们据此手动设置打断标记为true

        如果是在程序正常运行期间被打断的,那么打断标记就被自动设置为true。处理好这两种情况那我们就可以放心地来料理后事啦!

 实现代码:


public class TwoStepInterupter {
    Thread t1;
    public void start(){
        t1 = new Thread(() -> {
            while(true){
                if(Thread.currentThread().isInterrupted()){
                    System.out.println("处理后事");
                    System.out.println("处理后事完毕,线程正常结束");
                    break;
                }
                System.out.println("监控器运行中,监控是否被打断");
                try{
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                    //sleep 被打断后会抛出错误 ,但是打断标记不会被设置为True 所以需要再次执行打断方法。
                    Thread.currentThread().interrupt();
                }
            }
        });
        t1.start();
    }

    public void stop(){
        t1.interrupt();
    }

    @Test
    public void test() throws InterruptedException {
        start();
        Thread.sleep(2000);
        stop();
    }
}

3.3.6 sleep,yiled,wait,join 对比

补充:

  1. sleep,join,yield,interrupted是Thread类中的方法

  2. wait/notify是object中的方法

sleep 不释放锁、释放cpu

join 释放锁、抢占cpu

yiled 不释放锁、释放cpu

wait 释放锁、释放cpu

 

3.4 守护线程

        默认情况下,java进程需要等待所有的线程结束后才会停止,但是有一种特殊的线程,叫做守护线程,在其他线程全部结束的时候即使守护线程还未结束代码未执行完java进程也会停止。普通线程t1可以调用t1.setDeamon(true); 方法变成守护线程;

        垃圾回收器线程就是一种守护线程

        Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

标签:Java,Thread,t1,线程,打断,JAVA,方法,sleep
来源: https://blog.csdn.net/Mr_Liuxz/article/details/121862365

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有