上下文切换是指CPU(中央处理单元)从一个进程或线程到另一个进程或线程的切换。
内核模式是CPU的特权模式,其中只有内核运行,并提供对 所有内存位置和所有其他系统资源的访问。其他程序(包括应用程序)最初在用户模式下运行,但 它们可以通过系统调用运行部分内核代码。
Kernel Mode(内核模式)
在内核模式下,执行代码可以完全且不受限制地访问底层硬件。它可以执行任何CPU指令和引用任何内存地址。内核模式通常为操作系统的最低级别、最受信任的功能保留。内核模式下的崩溃是灾难性的;他们会让整个电脑瘫痪。
User Mode(用户模式)
在用户模式下,执行代码不能直接访问硬件或引用内存。在用户模式下运行的代码必须委托给系统api来访问硬件或内存。由于这种隔离提供的保护,用户模式下的崩溃总是可恢复的。在您的计算机上运行的大多数代码将在用户模式下执行。
应用程序一般会在以下几种情况下切换到内核模式:
1. 系统调用。
2. 异常事件。当发生某些预先不可知的异常时,就会切换到内核态,以执行相关的异常事件。
3. 设备中断。在使用外围设备时,如外围设备完成了用户请求,就会向CPU发送一个中断信号,此时,CPU就会暂停执行原本的下一条指令,转去处理中断事件。此时,如果原来在用户态,则自然就会切换到内核态。
这五态分别是:**初始状态、可运行状态、运行状态、休眠状态和终止状态 **
方式1:使用 Thread类或继承Thread类
//创建线程
Thread thread = new Thread() {
@Override
public void run() {
//执行的任务
}
};
//运行线程
thread.start();
方式2:实现 Runnable 接口配合Thread
Runnable runnable = new Runnable() {
@Override
public void run() {
//执行的任务
}
};
Thread thread1 = new Thread(runnable);
thread1.start();
方式3:使用有返回值的 Callable
class CallableTash implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return new Random().nextInt();
}
}
//创建线程池
ExecutorService execut = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
Future<Integer> result = execut.submit(new CallableTash());
方式4:使用 lambda
Thread thread2 = new Thread(() -> {/**执行的任务*/}, "thread");
//运行线程
thread2.start();
本质上Java中实现线程只有一种方式,都是通过new Thread()创建线程,调用Thread#start启 动线程最终都会调用Thread#run方法
线程创建和启动的流程
Java线程执行为什么不能直接调用run()方法,而要调用start()方法?
直接调用run()方法仅仅简单的调用对象的方法,而调用start()方法会调用本地方法栈,从而让操作系统新建新线程,最终让新线程执行run方法。
它们是依赖于内核的,即无论是用户进程中的线 程,还是系统进程中的线程,它们的创建、撤消、切换都由内核实现。
操作系统内核不知道应用线程的存在
线程执行时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另 外一个线程上。最大好处是实现简单,且切换操作对线程自己是可知的,没啥线程同步问题。坏处是线程执行时间不可控制,如果一个线程有问题,可能一直阻塞在那里。
每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中, Thread.yield()可以让出执行时间,但无法获取执行时间)。线程执行时间系统可控,也不会有 一个线程导致整个进程阻塞。
Java 语言中线程共有六种状态,分别是:
等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之 后才能继续运行的场景。
中断机制是一 种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。被 中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选 择压根不停止
API的使用
while (!Thread.currentThread().isInterrupted() && more work to do) {
do more work
}
例如:
class StopThread implements Runnable{
@Override
public void run() {
for (int i =0 ;
Thread.currentThread().isInterrupted() && i<1000;
i++){
System.out.println(i);
}
}
}
public static void main(String[] args) {
Thread thread3 = new Thread(new StopThread());
thread3.start();
Thread.sleep(5);
thread3.interrupt();
}
注意:
sleep可以被中断 抛出中断异常:sleep interrupted, 清除中断标志位
wait可以被中断 抛出中断异常:InterruptedException, 清除中断标志位
上述情况需要重新 线程中断状态为true
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
//重新设置线程中断状态为true
Thread.currentThread().interrupt();
}
volatile有两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线程 之间进行通信。
等待唤醒机制可以基于wait和notify方法来实现,在一个线程内调用该线程锁对象的wait方法, 线程将进入等待队列进行等待直到被唤醒。
LockSupport是JDK中用来实现线程阻塞和唤醒的工具,线程调用park则等待“许可”,调用 unpark则为指定线程提供“许可”。使用它可以在任何场合使线程阻塞,可以指定任何线程进行 唤醒,并且不用担心阻塞和唤醒操作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样 的。
class ParkThread implements Runnable{
@Override
public void run() {
System.out.println("程序正在执行");
System.out.println("程序将要进入等待.....");
LockSupport.park();
}
}
// main...
Thread parkThread = new Thread(new ParkThread());
parkThread.start();
System.out.println("唤醒parkThread");
LockSupport.unpark(parkThread);
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程 之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括了如下4种具体实现: PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节, 而后两种面向字符。
join可以理解成是线程合并,当在一个线程调用另一个线程的join方法时,当前线程阻塞等 待被调用join方法的线程执行完毕才能继续执行,所以join的好处能够保证线程的执行顺序,但 是如果调用线程的join方法其实已经失去了并行的意义,虽然存在多个线程,但是本质上还是串 行的,最后join的实现其实是基于等待通知机制的