架构师养成记-JUC

线程基础

并发与并行

并发,concurrency,是一段时间内处理多件事情的需求,它是问题域problem domain的概念。需求

并行,parallelism,是在同一时刻同时处理多件事情的方式,它是方法域solution domain的概念。 解决方案

并发是问题,是需求;并行是解决并发问题的方法之一

 进程与线程

操作系统将内存分配给不同的进程,将CPU分配给不同的线程;同一个进程内有多个线程可以共享该进程分配到的内存,而CPU以线程为单位在不同线程间不断切换执行。

创建线程的方式

创建任务并加载到Thread,有三种方式:demo

  • 继承Thread
  • 实现Runnable
  • 实现Callable  可以 使用Future获取Callable返回值   使用FutureTask获取Callable返回值

操作系统线程状态和JVM线程状态进行梳理

 解释:

  • 线程创建后会进入ready就绪状态。
  • 操作系统将CPU时间切分为一个一个连续的周期,比如10~20ms,称为CPU时间分片,然后按照这个分片轮转地选择就绪状态的线程去执行,被选择的线程进入running状态。
  • 当一次时间分片结束,操作系统会发出一个中断信号(interrupt),通知CPU中断当前running状态的线程,将其退回就绪状态,并重新从就绪状态的线程中选择一个执行。即CPU切换线程。
  • 当某个running的线程执行到IO操作(比如读写磁盘)时,该线程会退出CPU,不再运行在CPU上,而是运行在磁盘设备上;从CPU的角度而言,它进入了waiting等待/阻塞状态;习惯上我们称这种状态为IO阻塞,但其实对线程而言只是执行它的地点从CPU换到了磁盘或其他IO设备上了而已。此时CPU当然会选取其他就绪线程去执行以避免CPU资源浪费。
  • 当IO操作结束时,对应线程从waiting状态变为就绪状态,操作系统也会给CPU发出一个中断信号(interrupt),通知CPU再次切换线程。

当然这5种状态只是一个抽象或者说总结,实际上不同的操作系统对线程状态的划分和命名不尽相同。比如waiting,在Linux操作系统中,就对应着S(浅度睡眠)、D(深度睡眠)、T(暂停态)等状态,总之我们将其理解为还可能再次运行的,目前因为IO阻塞等原因不占用CPU的线程状态即可  cpu视角定义线程的状态

jvm层面的线程状态

  • Thread.State这个枚举中可以看到Java定义的6种线程状态:

    • NEW
    • RUNNABLE
    • BLOCKED
    • WAITING
    • TIMED_WAITING
    • TERMINATED

 这段代码能够很好的说明 线程之间的状态转换

private void testReenterBlocked() {
    class EchoPrinterAgain {
        // synchronized修饰实例方法,则锁为 this,即EchoPrinter的一个实例
        public synchronized void echoPrint1() {
            Scanner scanner = new Scanner(System.in);
            System.out.println("我是胡汉三,我要准备跑路了,随便说点啥吧:");
            String content = scanner.nextLine();
            System.out.println(content);
            try {
                // 让出锁,当前JAVA线程进入WAITING状态
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我胡汉三又回来了。。。over。。。");
        }

        public void echoPrint2() {
            System.out.println("我是潘冬子,准备抢锁,按导演的计划,我会失败...");
            // 使用 this 作为锁,即EchoPrinter的一个实例
            synchronized (this) {
                System.out.println("我是潘冬子,我抢到锁了...");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("我是潘冬子,导演让我喊胡汉三回来...");
                this.notifyAll();
                System.out.println("我是潘冬子,我唤醒了胡汉三,等我休息三秒...");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("我是潘冬子,我即将退出舞台。。。");
            }
        }
    }

    // 因为锁是EchoPrinter的一个实例,这里需要先生成实例
    EchoPrinterAgain echoPrinterAgain = new EchoPrinterAgain();

    // 创建线程1并启动,线程1将运行echoPrint方法
    Thread t1 = new Thread(echoPrinterAgain::echoPrint1);
    t1.start();

    // 主线程等待1秒钟,以确保线程1启动
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // 创建线程2并启动,线程2将运行print方法
    Thread t2 = new Thread(echoPrinterAgain::echoPrint2);
    t2.start();

    // 主线程每隔一秒打印一次两个子线程的状态
    while (true) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(String.format("线程 %s 的状态 %s", "胡汉三", t1.getState().toString()));
        System.out.println(String.format("线程 %s 的状态 %s", "潘冬子", t2.getState().toString()));

        if (Thread.State.TERMINATED.equals(t1.getState())
                && Thread.State.TERMINATED.equals(t2.getState())) {
            break;
        }
    }
}
问题1 为什么执行wait方法的线程进入了WAITING状态而不是直接进入BLOCKED状态?

 守护线程

守护线程的概念很简单,它不是线程状态,而是线程的一种属性。当一个线程被设置为守护线程时,只要主线程结束,该守护线程就会结束。

示例参考之前【BLOCKED状态】的第一段代码,将其中的t1.setDaemon(true);t2.setDaemon(true);从注释中释放出来,就会发现,主线程打印出t1和t2的状态后就会直接结束JVM,不会因为t1和t2尚在运行或阻塞中就也无法结束。

CompletableFuture(jdk 8)

future接口  futureTask就是 future的一个实现  为什么futuretask 可以返回数据   支持异步多线程(future)  有返回值(callable)  实现runable接口

class mythread implements callable<String> { @override public String call() {return "dfasd”}}

FutureTask<String> task = new FutureTask<>(new mythread()); task.s

缺点:通过get获取结果会阻塞主线程   2:通过轮询的方式判断是否完成  isdone  占用cpu

所以出现了 completableFuture  一个Task编排工具

 

 

 
posted @ 2023-11-24 14:29  一滴水滴  阅读(20)  评论(0)    收藏  举报