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


在这里插入图片描述

线程运行原理

栈内存

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

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

线程的上下文切换

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

  1. 线程的CPU时间片用完

  2. 垃圾回收

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

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

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

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

在这里插入图片描述

常见方法

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重新竞争。

在这里插入图片描述

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
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木 木 水.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值