1、认识线程
(1)概念
线程是被包在进程中的。一个进程默认会有一个线程【主线程】,但也可以有多个线程。
一个线程就是一个执行流,可以单独的在CPU上进行调度。
同一个进程中的各个线程,共享一份系统资源(内存+文件),节省了资源分配的开销。
举个例子,一家公司要去银行办理业务,既要进行财务转账,又要交社保。如果只有一个会计,耗费时间长。为了让业务更快完成,该会计又喊来一位助手帮助他。两人分别负责一个事情,分别申请一个号码进行排队;此时就有两个执行流共同完成任务。本质都是办理一个公司的业务。其中,该会计是主线程。
多线程:将一个大人物分解成不同的小任务,交给不同的执行流去排队执行。
(2)线程的意义
->首先,并发编程成为刚需。单核CPU的发展遇到瓶颈,想要提高算力,就需要多核CPU。而并发编程能够充分利用多核CPU资源。
->虽然多进程也能实现并发编程,但是线程比进程更轻量。创建线程、调度线程、销毁线程都比创建进程、调度进程、销毁进程快。在创建第一个线程时,已经申请了系统资源,所以在后续创建新的线程,都是共用一份系统资源,节省了申请系统资源的开销。
(3)进程与线程的区别
->首先,进程包含线程。每个进程中至少包含一个线程,即主线程。【一组PCB来描述一个进程,每个PCB对应一个线程】
->其次,线程比进程更轻量。创建线程、调度线程、销毁线程都比创建进程、调度进程、销毁进程快。
->主要的,进程之间不能共享内存空间等资源,而一个进程中的不同线程可以共享同一份内存空间。【这一组PCB上,内存指针与文件描述符表是共享的同一份;状态,优先级,上下文等是每个线程独自有一份】
->进程是系统分配资源的最小单位,线程是系统调度的最小单位。
2、创建线程
方法1:继承Thread类,重写run()方法
在第2步中,创建CreatThread类的实例时,并不会创建一个线程。只有当thread.start()执行之后,才会创建一个新的线程,然后执行run()中的逻辑代码,知道执行完,新创建的线程就会运行结束。
值得注意的是,Main主线程和CreatThread创建出来的新线程,是并发执行的关系。
多线程中,线程的 调度,其实是随机的。->抢占式执行。
方法2:实现Runnable接口,重写run()
方法3:使用匿名内部类,创建Thread子类对象
方法4:使用匿名内部类,创建Runnable子类对象
多线程的优势:能够更充分的利用多核CPU,提高程序效率。
3、Thread 类及常见方法
(1)Thread 的常见构造方法
Thread() ----> 创建线程对象
Thread(Runnable target) ---->使用 Runnable 对象创建线程对象
Thread(Runnable target, String name) ---->使用 Runnable 对象创建线程对象,并命名
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("线程名");
Thread t4 = new Thread(new MyRunnable(), "线程名");
(2)Thread 的几个常见属性
ID ---->获取方法:getId()
名称 ---->获取方法:getName()
状态 ---->获取方法:getState()
优先级 ---->获取方法:getPriority()
是否后台线程 ---->获取方法:isDaemon()
是否存活 ---->获取方法:isAlive()
是否被中断 ---->获取方法:isInterrupted()
注意以下几点:
-》ID是线程的唯一标识,每个线程都不同
-》关于后台线程,JVM会在一个进程的所有非后台进程结束后,才会结束运行。
-》 关于是否存活,简单理解为 run()方法是否运行结束
结果:
(3)Thread类中run()和start()的区别
作用功能不同:run方法,描述县城具体要执行的任务;start方法,真正申请系统线程。
运行结果不同:run方法相当于是一个类中的普通方法,可以被调用,调用后会顺序执行一次。start被调用后,真的在操作系统底层创建出一个线程,并执行run方法中的代码,run方法执行完成后,线程进入销毁阶段。
4、中断一个线程
就是控制线程结束,本质是让run方法尽快结束,而不是执行一半,强制结束。
方法1:自定义一个标志位,作为线程是否结束的标志
方法2:使用标准库中自带的一个标志位
使用 thread 对象的 interrupted() 方法通知线程结束.
Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记。
方法:
(2)分析
interrupt方法的行为,有两种:
当t线程处在运行状态,会设置标志位为true;
当t线程处于阻塞状态,则以 InterruptedException 异常的形式通知,该异常会把sleep提前唤醒;出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程。
(3)观察标志位是否清除
5、等待一个线程 -join()
控制线程之间的结束顺序。
上述,让main线程阻塞等待。等到t线程执行结束,main线程才会继续执行。
但如果是在join()之前,t线程已经结束,此时main线程不需要阻塞等待。
方法:
6、休眠当前线程
sleep(),指定休眠时间,让线程阻塞一会。
擦偶走系统管理这些线程的PCB时,是有多个链表。