深入学习JUC,深入了解Java线程的常见方法与底层原理,带你了解从未深入的底层!!!

文章目录

      • 线程运行原理
        • 栈内存
        • 线程的上下文切换
      • 常见方法
        • start()
        • run()
        • join()/join(n)
        • setPrioritty(int)/getPrioeity()
        • getState()
        • interrupted()/isInterrupted()
        • currentThread()
        • sleep(long n)
        • yield()
      • 方法详解
        • 不推荐使用的方法
        • sleep和yield的区别
        • 线程的优先级
        • interrupt 与 park
        • 主线程和守护线程
        • Wait / Notify的原理
        • Sleep(n)和wait方法的区别(重点)
        • join的原理
        • Park与Unpark

深入学习JUC,深入了解Java线程的常见方法与底层原理,带你了解从未深入的底层!!!_第1张图片

线程运行原理

栈内存

每个方法对应一个栈帧,栈内存会在方法一结束就释放掉栈帧

JVM中由堆, 栈, 方法区所组成 , 栈中的内存给线程使用.
每个栈由多个栈帧组成, 对应着每次方法调用时所占的内存.
每个线程只能有一个活动栈帧, 对应着当前正在执行的那个方法.

线程的上下文切换

导致线程上下文切换的原因

  1. 线程的CPU时间片用完

  2. 垃圾回收

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

  4. 线程自己调用了sleep, yield , wait , join , park , synchronized, lock方法

发生上下文切换时, 当前线程的程序计数器会存放正在执行的虚拟机字节码指令的地址 ,同时局部变量, 操作数栈, 返回地址等等都会保存在栈中, 以便于切换时恢复使用.

上下文切换的成本很高, 需要恢复其他线程栈中的所有方法栈和局部变量, 同时需要保存当前线程栈中的所有方法栈和局部变量.

深入学习JUC,深入了解Java线程的常见方法与底层原理,带你了解从未深入的底层!!!_第2张图片

常见方法

start()

启动一个线程, 在线程内运行run方法中的代码

start方法只是让线程进入就绪状态, 里面的代码执不执行需要看CPU的时间片有没有分给他.
每个线程的start方法只能调用一次, 如果调用了多次就会出现异常.

run()
  1. 线程启动后会调用的方法
  2. 如果构造Thread对象时传递了Runable参数, 则线程启动后就会调用Runnable中的run方法, 否则执行自己重写的run方法.
  3. 启动一个线程时, 应该使用start方法, 虽然可以调用run方法也能拿到结果, 但是此时的run方法是由main线程执行的, 达不到异步的效果
join()/join(n)

等待一个线程结束, 或者指定一个超时时间,最多等待n毫秒

比如线程一需要线程二的结果, 需要在线程一当写上 t2.join()

此时t2就会一直抢占cpu资源, 直到线程结束

这样就能让线程一只能等待线程二结束,拿到结果之后才能运行

setPrioritty(int)/getPrioeity()

setPriority(int) 修改线程的优先级 , Java中线程优先级是 1 - 10 ,较大的优先级能提高线程被CPU调度的机率 , 只是提高几率, 具体谁先执行还是看操作系统.

getPriority() 获取当前线程的优先级

getState()

6 种状态

  • NEW : 新建状态 , 线程刚被创建, start之前的方法.
  • RUNNABLE : 当线程已被占用, 在虚拟机中正常的执行, 就处于此状态.
  • BLOCKED : 当一个线程试图获取一个对象锁时, 而该对象锁被其他的线程持有, 此时就进入了BLOCKED状态 , 线程获取到锁时, 就变成 了RUNNABLE状态
  • WAITING : 就是休眠状态, 一个线程等待另一个线程执行一个动作时,一般是wait(n), sleep(n), 该线程就进入了waiting状态, 这个状态是不能被唤醒的, 必须等待另一个线程调用notify()或者notifyAll方法才能唤醒
  • TIME_WAITING : 休眠一定时间的状态, 到了指定时间和受到唤醒通知(notify)时就会结束.
  • TERMINATED : 从RUNNABLE状态正常退出而死亡, 或者因为没有捕获的异常而终止了RUNNABLE状态而死亡.
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread thread=new Thread(()->{
            System.out.println("线程开始执行");
        },"线程1");
        System.out.println(thread.getState());   //NEW
        thread.start();
        System.out.println(thread.getState());  //RUNNABLE
        thread.join();	//此时线程一直占用直到结束
        System.out.println(thread.getState());  //TERMINATED
    }

interrupted()/isInterrupted()
  • interrupted() , 打断线程, 如果被打断的线程正在sleep , wait , join会导致被打断的线程抛出InterruptedExecption, 并且清除打断标记(打断标记为false). 如果打断正在运行的线程, 则会设置打断标记(打断标记设置为true).
  • isInterrputed() , 判断是否被打断, 不会清除标记.
currentThread()

获取当前正在执行的线程

sleep(long n)

sleep方法只能写在线程的内部, 就是调用Thread.sleep(n) , 写在什么线程内部就让他休眠
让当前执行的线程休眠n毫秒, 休眠时间让出cpu的时间片给其他线程

yield()

让步, Thread.yield()方法作用是:暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),并执行其他线程。
但是不一定保证他能起到让步的效果 , 因为CPU可能再次调度.

方法详解

不推荐使用的方法

stop , suspend (暂停线程) resume (恢复线程运行) 这三种都会造成线程死锁

sleep和yield的区别
  1. sleep会让当前线程从RUNNABLE进入TIMED_WAITING状态
  2. 其他线程可以打断正在睡眠的线程, 这时sleep方法会抛出InterruptedExecption的异常
  3. 睡眠结束后的线程不一定能立刻达到执行
  4. 一般项目中我调用Thread.sleep时 , 是同TimeUnit代替, 因为它可以让睡眠时间变得更有可读性

yield会让当前线程从Running进入Runnable状态, 然后调度执行其他同优先级的线程. 如果这时没有同优先级的线程, 那么就不能保证让当前线程暂停的效果

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread thread=new Thread(()->{
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("线程内方法线程开始执行");
        },"线程1");
        System.out.println(thread.getState());  //NEW
        thread.start();
        System.out.println(thread.getState());  //RUNNABLE
        TimeUnit.SECONDS.sleep(1);		//主线程先休眠一秒再执行打印thread的线程状态
        System.out.println(thread.getState());  //TIMED_WAITING
        thread.join();
        System.out.println(thread.getState());  //TERMINATED
    }


线程的优先级

线程的优先级为1-10 , 默认优先级为5
线程优先级会提示调度器优先调度该线程, 但它仅仅是一个提示, 调度器可以忽略它
如果cpu比较忙, 那么优先级高的线程会获得更多的时间片, 但是cpu闲的时候, 优先级就没了作用

interrupt 与 park

interrupt

  • 线程对象调用 interrupt , 会把打断标记设置为true , 但是在sleep状态下被打断, 打断标记会设置为false.
    当前线程调用Thread.interrupted(), 如果标记为true , 清除设置为false , 如果为false则不做改变
    park是LockSupport的方法, 也会打断线程, 但是准确来说应该是暂停线程 , 线程会一直等待直到打断标记为false.
    打断标记为true的时候, park方法不会生效
  • 线程本身调用interrupt , 判断打断标记是否为false , 是的话就设置为true
    Thread.interrupt , 判断当前线程是否为ture , 是的话则将其设置为false
public class ThreadCreate {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            System.out.println("开始执行");		//线程一开始, 打断标记为false
            System.out.println(Thread.interrupted());	//获取当前标记为false, 不做改变
            System.out.println("继续执行,打断标记为: "+Thread.currentThread().isInterrupted());  //仍未false   
            LockSupport.park();  // 发现打断标记为false , 暂停线程  ,由主线程打断
            System.out.println("继续执行,打断标记为: "+Thread.currentThread().isInterrupted());  //被打断, 标记未true
            System.out.println(Thread.interrupted());	//发现当前标记未true,返回, 清除并设置未false
            System.out.println("继续执行,打断标记为: "+Thread.currentThread().isInterrupted());  //此时未false
            LockSupport.park();	// 发现打断标记为false , 暂停线程  ,由主线程打断
            System.out.println("没有park就执行");  //线程被暂停, 一直等待打断
        });
        thread.start();
        TimeUnit.SECONDS.sleep(2);
        thread.interrupt();
    }
}
主线程和守护线程

默认情况下, java进程只有当所有进程都结束之后, 才会结束 .
守护进程就是只要其他非守护进程运行结束了, 即时守护线程的代码没有执行完, 也会强制结束.

垃圾回收线程就是一个守护线程, 如果程序停止, 垃圾回收器就会强制结束
Tomcat的acceptor和Poller线程也都是守护线程 , 当Tomcat受到shutdown时, 就会强制结束

public class ThreadCreate {
    public static void main(String[] args) throws InterruptedException {
      Thread thread=new Thread(()->{
          while (true){
              if (Thread.currentThread().isInterrupted()){
                  break;
              }
          }
          System.out.println("进程执行");		//守护进程的代码没有执行完也会随着非守护进程的结束而结束
      });
      thread.setDaemon(true);		//设置为守护线程, 当其他线程执行完了, 就会强制结束
      thread.start();
      System.out.println(Thread.currentThread().getName()+"结束");
    }
}
Wait / Notify的原理
  1. 线程竞争到锁之后,就是Monitor里面的Owner为自身的时候,发现运行条件不足,就会调用wait()方法,进入WaitSet变成WAITIONG状态
  2. BLCOKED和WAITING的线程都处于阻塞状态,不占用CPU时间片
  3. BLOCKED线程会在Owner线程释放时唤醒
  4. WAITING线程会在Owner线程调用notify或者notifyAll时唤醒,但是唤醒后并不意味这立刻获得锁,仍需进入EntryList重新竞争。

深入学习JUC,深入了解Java线程的常见方法与底层原理,带你了解从未深入的底层!!!_第3张图片

Sleep(n)和wait方法的区别(重点)
  1. sleep是Thread方法,而wait是Object方法,即所有对象都有wait方法,而sleep只有当前线程才有。
  2. sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起使用。因为只能拿到锁对象了,就是Owner里面的线程才能唤醒waitSet里面的线程
  3. sleep在睡眠的时候,不会释放锁,但是wait在等待的时候会释放锁,唤醒后会进入EntrySet阻塞竞争锁
  4. sleep只能规定睡眠的时间,wait可以无限等待和有时限的等待
  5. sleep和时限的wait的线程状态都是Time_Waiting.
join的原理

**调用alive方法, 判断线程是否存活, 如果存活, 则调用不限制等待Wait()的方**法 ,进入锁的waitset,如果线程结束, 则唤醒.

Park与Unpark

park对应WAITING状态

  1. wait , notify 和 notifyAll 必须配合 Object Monitor一起使用, 而park, unpark不需要

  2. park 和 unpark是以线程为单位来阻塞和唤醒线程, 而notify只能唤醒一个等待线程, 而notifyAll可以唤醒所以等待线程, 就不那么精确

  3. park 和 unpar可以先unpark, 而wait 和 notify就不能先notify.

原理

每个线程都有自己的一个parker对象, 由三部分组成 _counter, _cond_mutex.

调用park方法时

  1. 检查_counter, 为0时,获得_mutex互斥锁
  2. 线程进入_conf条件变量阻塞
  3. 设置_counter=0

调用unpark方法时

  1. 设置_counter为1
  2. 唤醒_cond条件变量中的Thread_0
  3. Thread_0恢复运行
  4. 恢复运行之后,设置_counter为0

先调用unpark, 再调用park方法时

  1. 调用unpark,设置_counter为1
  2. 当前线程调用park方法
  3. 检查_counter为1, 就无需阻塞, 继续运行
  4. 设置_counter为0

你可能感兴趣的:(JUC的深入学习,学习,java,开发语言)