进程 多任务操作系统
描述 : PCB 组织 : 链表
线程 轻量级进程
同一个进程的多个线程共享一份系统资源( 创建线程,就省去了申请资源开销. 销毁线程,也省去了资源释放的开销.)
每个线程,都是一个独立的执行流,都可以独自参与CPU的调度.
每个进程,就会包含多个线程了,多个线程共享同一份资源(内存 + 文件描述符表)
操作系统为我们在编程时提供了一些操作线程的api,由于操作系统都是C/C++实现的,所以提供的api也都是C/C++的
而在我们java编程时,JDK就对这些系统提供的api进行了封装,封装成了java风格的api
Thread类和run方法,可以用来操作线程的
run方法,是一个线程的入口,没有run方法,线程没有入口,就无法创建线程.
一、操作系统"内核"
内核,是操作系统最核心的功能模块,( 管理硬件 , 给软件提供稳定的运行环境 )
1.1 内核态和用户态
我们平常所运行的应用程序,如:浏览器,网易云音乐等等的,都是在运行用户态了,
那么,为什么要划分出内核态和用户态呢?
这主要还是为了稳定.防止你运行的应用程序出了什么错误,把硬件设备或软件资源给弄出问题了.
系统封装了一些api,这些api都是属于一些合法的操作,应用程序,只能调用这些api,就不至于对系统/硬件设备,产生太大的危害.
操作系统=内核+配套的应用程序.
二、线程
每一个线程,都是一个独立的执行流,每个线程都能独立的去CPU上调度执行.
按照之前所学习的只是,如果代码中出现了死循环,就会被卡在死循环这里,而无法执行后面的代码.
而现在,在我们引入多线程后,如果一个线程中出现了死循环,是不会影响到另一个线程中代码的执行的.
class MyThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("线程1");
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
while(true){
System.out.println("线程2");
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class MyThread1 {
public static void main(String[] args) {
Thread t1 = new MyThread();
t1.start();
Thread t2 = new MyThread2();
t2.start();
}
}
上面的这个代码,可以发现,他一直在死循环的打印 "线程1" 和 "线程2"
也就是说,不同的线程,他们的执行是独立的,互不干扰的.
并且,这些线程的执行顺序是不确定的.这是因为,操作系统中有一个调度器的模块,这个模块的实现方式,是一种类似随机调度的方式.
随机调度:一个线程,到CPU上执行和从CPU上下来的时机都是不确定的.因为线程在CPU上是"抢占式执行的.
除了用打印来看线程,我们还可以使用第三方软件来查看线程.
在jdk中,有一个工具jconsole,它可以帮我们查看正在运行的线程.
在上面,我们还使用了,sleep函数.
Sleep是windows的api提供的函数,我们在进行java编程的时候,可以使用java自己封装的Thread.sleep函数.
在使用sleep函数的时候,我们还需要抛出一个异常,因为比如上面的sleep的传参是1000,而在sleep1000的过程中可能会被提前唤醒.
并且,在run方法中只能使用try,因为如果在run中用throws的话,会导致方法的方法签名被修改了,就无法构成重写了.
三、线程的几种方式
3.1 继承Thread重写run
class MyThread01 extends Thread{
@Override
public void run() {
System.out.println("MyThread01");
}
}
public class MyThreadDemo1 {
public static void main(String[] args) {
Thread t1 = new MyThread01();
t1.start();
}
}
3.2 实现Runnable接口,重写run
class MyThread02 implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable接口重写run");
}
}
public class MyThreadDemo2 {
public static void main(String[] args) {
Runnable runnable = new MyThread02();
Thread t1 = new Thread(runnable);
t1.start();
}
}
3.3 继承Thread类重写run,但是使用匿名内部类
public class ThreadDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("内部类线程");
}
};
t1.start();
}
}
3.4 实现Runnable,重写run,匿名内部类
public class ThreadDemo4 {
public static void main(String[] args) {
Thread t1 =new Thread(new Runnable(){
@Override
public void run() {
System.out.println("匿名内部类,实现Runnable重写run");
}
});
t1.start();
}
}
3.5 使用lambda表达式 [比较常用]
public class ThreadDemo5 {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
System.out.println("lambda表达式");
});
t1.start();
}
}
四、Thread的重要属性和方法
4.1 方法
1. Thread() 创建线程对象
2. Thread(Runnable target) 使用Runnable对象创建线程对象
3. Thread(String name) 创建线程对象,并命名
4. Thread(Runnable target,String name) 使用Runnable对象创建线程,并命名.
5. Thread(ThreadGroup group,Runnable target) 线程可以被用来分组管理,分好的组即为线程组.
线程之间的名字是可以重复的.
4.2 属性
1.ID
获取方法 : getID()
2.名称
获取方法 : getName()
3.状态
获取方法 : getState();
4.优先级
获取方法 : getPriority()
5.是否后台进程
获取方法 : isDaemon()
6.是否存活
获取方法 : isAlive()
7.是否被中断
获取方法 : isInterrupted();
后台线程与前台线程相对,前台线程的运行,会阻止进程的结束,后台线程的运行,不会阻止进程结束.
我们创建的线程默认为前台线程,会阻止进程结束.只要前台线程没有执行完,进程就不会结束.(即使main已经执行完毕)
setDaemon(true) -> 在start之前,设置线程为后台线程.
true -> 后台线程
isAlive() -> 表示了,进程中是否还有线程的存在.
Thread t1 = new Thead() ->此时t1对象有了,但是内核PCB还没有,isAlive()就是false.
t1.start() -> 这之后,内核中创建出PCB,此时isAlive就是true.
当run执行完了,此时内核中的线程就结束了,此时t1变量可能还存在,此时isAlive()就是false.