java线程基础-生命周期,创建合适线程数,局部方法安全性

线程生命周期

首先先来介绍线程通用生命周期

通用的线程生命周期

在这里插入图片描述

  1. 初始状态:线程被创建(仅仅是编程语言层面,操作系统还没有创建真正的线程),不允许CPU分配资源。
  2. 可运行状态:线程可以分配CPU执行,(操作系统层面已经创建线程了)
  3. 运行状态:操作系统给其分配一个可运行状态的线程,占用CPU使用权。
  4. 休眠状态:运行状态调用阻塞API或者等待某个条件变量成立,释放CPU使用权,陷入休眠状态。
  5. 终止状态:线程执行完活出现异常就会进入终止状态,线程生命周期也就结束了。

java线程的生命周期

在这里插入图片描述
java因为操作系统中线程非常稳定,因此把JVM把线程调度交给操作系统了,因此java把可运行状态和运行状态合并了。

而休眠状态中java有三个状态BLOCKED(阻塞状态),WAITING(无实现等待),TIMED_WAITING(有时限等待),java线程处于这三种状态时,就永远没有CPU的使用权。

那么java线程之间的状态时如何转化的?

  1. RUNNABLE和BLOCKED之间的转换
    只有一种场景(就是线程等待获取synchroned隐式锁,synchronized修饰的方法和代码块只能一个线程进入,其他线程会陷入BLOCKED,而当等待的线程获取到synchronized隐式锁,就会从BLOCKED转换成RUNNABLE)会触发这种转换。
    那平时调用阻塞式API会不会转换到BLOCKED状态呢?
    答:不会,但是在操作系统层面是会转换到休眠状态,JVM中不会发生变化,还是RUNNABLE,因为把线程调度交给操作系统了,在JVM中等待CPU使用权和等待IO,都是等待CPU使用权,归为RUNNABLE。

  2. RUNNABLE和WAITING之间的转换
    三种场景会触发

    1. 获取synchronized隐式锁,调用wait()方法。RUNNABLE->WAITING
    2. 调用Thread.join,执行这条语句的线程会等待该Thread线程执行完,等待中的线程会从RUNNABLE->WAITING。而等Thread执行完,就会WAITING->RUNNALBE。
    3. thread调用LockSupport.park()当前线程thread会被阻塞(从RUNNABLE->WAITING),调用LockSupport.unpark(Thread thread)可唤醒thread,thread的状态WAITING->RUNNABLE。
  3. RUNNABLE和TIMED_WAITING的状态转换
    五钟场景会触发这种情况(都是调用带超时参数的方法)

    1. 调用Thread.sleep(long mills)
    2. 获得synchronized隐式锁后调用wait(long time)。
    3. thread调用Thread.join(long time)。thread陷入超时等待中(时间一到继续执行),而Thread运行。
    4. 调用带超时参数的 LockSupport.parkNanos(Object blocker, long deadline) 方法;
    5. 调用带超时参数的 LockSupport.parkUntil(long deadline) 方法。
  4. NEW到RUNNABLE状态
    NEW状态:Thread thread=new Thread();不会被操作系统调度。
    RUNNABLE状态:thread.start()。线程调度交给操作系统(阻塞还是执行)。

  5. 从 RUNNABLE 到 TERMINATED 终止状态
    当线程执行完时,自动从RUNNABLE->TERMINATED。在执行run方法时抛出异常,也会进入终止状态。

    有时想要终止线程,正确姿势是调用interrupt()方法,那为什么不调用stop呢?

那么stop和interrupt的区别?

Thread.stop()方法,会直接终止线程,并且释放对应拿到的锁。
而interrupt()方法仅仅是通知线程。

  • 当线程处于WAITING和TIMED_WAITING状态时,调用interrupt方法,会使该线程变成RUNNABLE状态,同时触发interruptException异常,并且清除中断标志位。
  • 当线程处于RUNNABLE且没有获取CPU资源时(比如阻塞在io),也会抛出对应的异常。

上面两种情况都是通过抛出异常,然后我们通过try,catch捕获异常,break可以终止线程。

  • 当线程处于RUNNABLE状态并且拥有CPU的资源,这时调用thread.interrupt()方法,只会通知,并不会抛出异常,只能通过isInterrupt()方法,检测是否通知中断了。注意:__如果抛出异常,中断标志位会被清除。__你可以在try,catch后重新添加标志位。

创建多少线程才是合适的

我们使用多线程的目的是为了提高计算机CPU和IO设备的利用率。

比如创建两个线程,在一个线程执行CPU计算,另一个线程执行IO操作。这样两者的利用率达到100%。

根据CPU和IO的执行时间,可分两种场景:

  • IO密集型:IO设备的执行时间相对CPU来说非常长
    最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]如果CPU和IO的耗时比是1:2, 3个线程是最合适的。
  • CPU密集型:大部分都是纯CPU计算
    理论上:线程数=CPU数量。

局部变量是线程安全的

我们都知道JAVAnew出来的对象都是放在共享堆区中,引用在放在独立栈中。

并且每个线程都有自己独立的栈空间,而这个栈是和方法的调用是有关系的,经常被称为调用栈。比如方法A调用方法B,程序运行时,会构建对应线程的调用栈,执行方法A,在调用栈中创建独属于方法A的空间,称为栈帧。调用方法B时,创建该方法的栈帧,压入到调用栈中,方法结束时,弹出栈帧。

每个线程有独属于线程的空间调用栈,每个方法在调用栈中有属于自己的空间栈帧,局部变量存放在栈帧中,所以会发现局部变量是线程安全的。
注意:set(get()+1),先压set方法,在压入get(),get()方法执行完,弹出栈帧,返回参数,执行set()方法。

参考 极客时间-Java并发编程
更多可见 邓新

发布了34 篇原创文章 · 获赞 0 · 访问量 1089

猜你喜欢

转载自blog.csdn.net/qq_42634696/article/details/104854573
今日推荐