文章目录
线程运行原理
栈内存
每个方法对应一个栈帧,栈内存会在方法一结束就释放掉栈帧
JVM中由堆, 栈, 方法区所组成 , 栈中的内存给线程使用.
每个栈由多个栈帧组成, 对应着每次方法调用时所占的内存.
每个线程只能有一个活动栈帧, 对应着当前正在执行的那个方法.
线程的上下文切换
导致线程上下文切换的原因
-
线程的CPU时间片用完
-
垃圾回收
-
有更高优先级的线程需要运行
-
线程自己调用了sleep, yield , wait , join , park , synchronized, lock方法
发生上下文切换时, 当前线程的程序计数器会存放正在执行的虚拟机字节码指令的地址 ,同时局部变量, 操作数栈, 返回地址等等都会保存在栈中, 以便于切换时恢复使用.
上下文切换的成本很高, 需要恢复其他线程栈中的所有方法栈和局部变量, 同时需要保存当前线程栈中的所有方法栈和局部变量.
常见方法
start()
启动一个线程, 在线程内运行run方法中的代码
start方法只是让线程进入就绪状态, 里面的代码执不执行需要看CPU的时间片有没有分给他.
每个线程的start方法只能调用一次, 如果调用了多次就会出现异常.
run()
- 线程启动后会调用的方法
- 如果构造Thread对象时传递了Runable参数, 则线程启动后就会调用Runnable中的run方法, 否则执行自己重写的run方法.
- 启动一个线程时, 应该使用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的区别
- sleep会让当前线程从RUNNABLE进入TIMED_WAITING状态
- 其他线程可以打断正在睡眠的线程, 这时sleep方法会抛出InterruptedExecption的异常
- 睡眠结束后的线程不一定能立刻达到执行
- 一般项目中我调用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的原理
- 线程竞争到锁之后,就是Monitor里面的Owner为自身的时候,发现运行条件不足,就会调用wait()方法,进入WaitSet变成WAITIONG状态
- BLCOKED和WAITING的线程都处于阻塞状态,不占用CPU时间片
- BLOCKED线程会在Owner线程释放时唤醒
- WAITING线程会在Owner线程调用notify或者notifyAll时唤醒,但是唤醒后并不意味这立刻获得锁,仍需进入EntryList重新竞争。
Sleep(n)和wait方法的区别(重点)
- sleep是Thread方法,而wait是Object方法,即所有对象都有wait方法,而sleep只有当前线程才有。
- sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起使用。因为只能拿到锁对象了,就是Owner里面的线程才能唤醒waitSet里面的线程
- sleep在睡眠的时候,不会释放锁,但是wait在等待的时候会释放锁,唤醒后会进入EntrySet阻塞竞争锁
- sleep只能规定睡眠的时间,wait可以无限等待和有时限的等待
- sleep和时限的wait的线程状态都是Time_Waiting.
join的原理
**调用alive方法, 判断线程是否存活, 如果存活, 则调用不限制等待Wait()的方**法 ,进入锁的waitset,如果线程结束, 则唤醒.
Park与Unpark
park对应WAITING状态
-
wait , notify 和 notifyAll 必须配合 Object Monitor一起使用, 而park, unpark不需要
-
park 和 unpark是以线程为单位来阻塞和唤醒线程, 而notify只能唤醒一个等待线程, 而notifyAll可以唤醒所以等待线程, 就不那么精确
-
park 和 unpar可以先unpark, 而wait 和 notify就不能先notify.
原理
每个线程都有自己的一个parker对象, 由三部分组成 _counter
, _cond
和_mutex
.
调用park方法时
- 检查_counter, 为0时,获得_mutex互斥锁
- 线程进入_conf条件变量阻塞
- 设置_counter=0
调用unpark方法时
- 设置_counter为1
- 唤醒_cond条件变量中的Thread_0
- Thread_0恢复运行
- 恢复运行之后,设置_counter为0
先调用unpark, 再调用park方法时
- 调用unpark,设置_counter为1
- 当前线程调用park方法
- 检查_counter为1, 就无需阻塞, 继续运行
- 设置_counter为0