本文用于记录学习黑马JUC课程后,个人认为有价值的笔记,核心内容均围绕标题展开。既做一个学习的记录,同时也做一个沟通交流,欢迎各位大佬互动~
创建和运行线程
方法一:使用Thread类创建对象(覆盖run,run方法内包含需要运行的代码,start方法启动线程)
方法二:使用Runnable(可运行的任务)配合Thread(线程和任务分离), Runnable接口为函数式接口
Runnable runnable = new Runnable(){
public void run(){
//要执行的任务
}
}
Thread t = new Thread(runnable,’名字’);
t.start();
方法一和方法二原理:方法一是重写Thread的run方法,而方法二是将runnable对象传递给Thread的方法(更好和线程池的高级api.)
方法三:FutureTask配合Thread,FutureTask能够接受Callable类型的参数,用来处理有返回结果的情况.
线程运行原理
线程运行原理: 类加载之后,代码缓存在方法区,等待cpu分配时间片,创建相应的线程(线程调度器负责创建和调度),在占用时间片的过程中,线程根据方法执行和调用的情况创建和删除相应的栈帧,期间伴随着或被动或主动的原因进行上下文切换,最终代码执行完毕线程关闭.
上下文切换:指由于一些原因cpu不在执行当前线程,转而执行另一个线程的代码.
导致上下文切换的原因有4个(3被动1主动):①线程的时间片用完;②产生垃圾回收;③有更高优先级的线程需要进行;④线程调用了sleep,yield,wait,join,park,synchronized,lock等方法
常见方法
Thread 类 API:
方法 | 说明 |
public void start() | 启动一个新线程(仅进入就绪状态,非立刻运行),Java虚拟机调用此线程的 run 方法.只能用一次,多次调用会产生错误. |
public void run() | 线程启动后调用该方法(被重写或者使用runnable对象) |
public void setName(String name) | 给当前线程取名字 |
public void getName() | 获取当前线程的名字 线程存在默认名称:子线程是 Thread-索引,主线程是 main |
public static Thread currentThread() | 获取当前线程对象,代码在哪个线程中执行 |
public static void sleep(long time) | 让当前线程休眠多少毫秒再继续执行 Thread.sleep(0) : 让操作系统立刻重新进行一次 CPU 竞争 |
public static native void yield() | 提示线程调度器让出当前线程对 CPU 的使用 |
public final int getPriority() | 返回此线程的优先级 |
public final void setPriority(int priority) | 更改此线程的优先级,常用 1 5 10 |
public void interrupt() | 中断这个线程,异常处理机制 |
public static boolean interrupted() | 判断当前线程是否被打断,清除打断标记 |
public boolean isInterrupted() | 判断当前线程是否被打断,不清除打断标记 |
public final void join() | 等待这个线程结束 |
public final void join(long millis) | 等待这个线程死亡 millis 毫秒,0 意味着永远等待 |
public final native boolean isAlive() | 线程是否存活(还没有运行完毕) |
public final void setDaemon(boolean on) | 将此线程标记为守护线程或用户线程 |
getState() | 获取线程状态, NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED |
start/run: ①区别; ②start改变进程状态; ③重复调用start
sleep: ①让当前线程从running进入到timed waiting状态; ②其他线程可用interrupt方法打断正在睡眠的线程,但会抛出异常; ③睡眠结束的线程未必立刻执行; ④TimeUnit的sleep比Thread的sleep具有更哈ode可读性
yield(本意是让的意思,cpu让给其他线程): 从running进入runnable
应用(sleep/yield)防止while(ture)空转浪费cpu,可用sleep/yield来让出cpu的使用权给其他程序
线程优先级:提示任务调度器优先调度该线程,仅做提示; t1.setPriority()
t1.jion: 等待线程运行结束(可以理解成t1加入主线程,并发执行)
应用(join,同步应用/限时同步): 等待多个线程的结果(调用方,调用多个其他线程的join方法)
interrupt(打断阻塞状态线程, 打断正在运行的线程)方法:
打断sleep,wait,join的线程(被打断后 打断标记清空,为假, t1.isInterrupted)
打断正常运行线程,不会直接打断,而是可以根据打断标记(Thread.currentThread().isInterruptedException()获取打断标记)决定是继续还是打断线程.
打断park线程,不会清空打断状态 park被打断之后 再调用park失效.(可以通过Thread.interrupted()方法,返回打断标记为真并重置打断标记为false,帮助后续park再次暂停)
主线程和守护线程
默认情况下,java虚拟机需要等待所有线程结束,才会结束. 有一种特殊的线程叫做守护线程,只要其他非守护线程运行结束了,即使守护线程的代码没执行完,也会强制结束. 通过启动前(t1.start())调用t1.setDaemon(),可将线程设置成守护线程
垃圾回收线程,也是一种守护线程
线程状态
从操作系统角度看,存在五种状态:
[初始状态] 仅语言层面创建了线程对象, 还未与操作系统线程关联
[可运行状态] (就绪状态) 已创建线程对象(也与操作系统关联了),可以由cpu调度执行
[运行状态] 获取了cpu时间片后 线程运行中的状态. 当时间片用完了就会切换至可运行状态,导致线程上下文切换
[阻塞状态] 调用了阻塞api后, 线程让出cup使用权, 任务调度器不会考虑将cpu的时间片分配给阻塞状态的线程,这也会导致线程的上下文切换. 结束后会进入可运行状态,并不是可以立刻进入运行状态.
[终止状态] 线程代码运行完了,生命周期结束,不会转换成其他状态
从Java API层面来看,存在六种状态:
NEW: 还未调用start()方法, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED.