多线程基础
线程进程
进程
每一个正在运行的程序
通过windows 任务管理器 查看正在运行的程序
- 特点
1- 进程是系统运行程序的基本单位
2- 每一个进程都有自己独立的一块内存空间,一组系统资源
3- 每一个进程内部的数据和状态都是完全独立的
线程
概念
线程是进程执行运算的最小单位, 一个进程在其执行过程中可以产生多个线程,线程必须在某个进程中执行.
线程是进程内部的一个执行单元, 是可完成一个独立任务的顺序控制流程,如果在一个进程中同时运行了多个线程,用来完成不同工作, 称之为多线程.
多线程的优势
迅雷多线程下载
- 最大限度的提高计算机系统的利用效率
- 可以带来更好的用户体验
线程和进程对比
1- 一个进程至少包含一个线程
2- 系统资源分配给进程, 同一进程中的所有线程共享该进程的所有资源
3- cpu 分配给线程执行机会,而不是分配给进程, 真正运行在cpu上的是线程
CPU 采用时间片段
一个CPU 同一时间只能做一件事
时间片段 : 每隔一段时间去执行一个程序, 时间片段使用完毕之后,再切换到另外一个程序, 因为这个切换速度很快, 用户感觉不到CPU 正在切换执行程序.
就好比说播放视频
视频 帧率 (本质就是一张一张的图片) 一秒内切换20 张图片.
连贯的执行图片切换,让用户感觉是在看视频.
用户感受不到时间粒度太细的操作
CPU 随机选择 一个线程去执行, 感觉上是多个线程是在争抢CPU 的执行权
线程的使用
在学习本章节之前, 所有的代码程序都是单线程的
无论代码有多复杂, 都是按照顺序依次执行的
main 函数线程
书写代码, 抛出异常
Exception in thread "main" java.lang.NullPointerException
- 主线程入口
- 开启其它子线程
- 最后执行各种关闭资源的操作
Thread
第一种创建线程的方法
1- 将一个类声明为Thread的子类。
2- 这个子类应该重写Thread类的run方法 。
3- 然后可以分配并启动子类的实例。
创建线程的步骤
1- 书写子类继承Thread 类
2- 重写 run 方法
3- 创建自定义的线程子类对象
4- 调用start() 方法
案例测试
public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0 ; i < 100 ; i++){
System.out.println("Mythread--"+i);
}
}
}
public class Demo2_Thread {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
for(int i = 0 ; i < 100 ; i++){
System.out.println("main--"+i);
}
}
}
start()
1- 启动一条线程
2- 执行该线程的run()方法
run() 和 start() 区别
run 仅仅是 线程类中的一个普通方法
如果使用线程对象调用run方法, 仅仅是在main 线程中 执行run() 方法中的代码
start() 才可以真正的启动线程
start() 可以自动去执行run() 方法
细节
多个线程执行, 不是真正的并行
线程每次执行的时长是由CPU 随机分配的时间片决定的
Thread常用方法
优先级
优先级高 有更高的机率可以获取CPU的更多时间片段, 不能保证一定优先执行
类似于掷骰子 1-4 张三 赢 56 是 李四赢, 真正玩游戏, 不能保证是张三
int getPriority()
返回此线程的优先级。
setPriority(int newPriority)
更改此线程的优先级。
设置线程名称
1- 有参构造 Thread(String name)
分配一个新的 Thread对象。
2- void setName(String name)
将此线程的名称更改为等于参数 name 。
String getName()
返回此线程的名称。
案例
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
// 子类如何调用执行 父类的构造方法
super(name);
}
}
new MyThread("线程1");
获取当前正在执行的线程
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
获取线程状态
Thread.State getState()
返回此线程的状态。
判断线程是否处于活跃状态
boolean isAlive()
测试这个线程是否活着。
线程休眠
static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
public void run() {
for(int i = 0 ; i < 100 ; i++){
// 获取当前线程名称
String sname = Thread.currentThread().getName();
if(i == 61){
// run 方法 是 子类重写的父类的方法, 子类抛出的异常不能超过父类的异常
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sname+"--"+i);
}
线程加入
void join()
等待这个线程死亡。
ThreadTest tt1 = new ThreadTest();
ThreadTest tt2 = new ThreadTest();
ThreadTest tt3 = new ThreadTest();
tt1.setName("西施");
tt2.setName("貂蝉");
tt3.setName("王昭君");
tt1.start();
/**join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
程序在main线程中调用tt1线程的join方法,则main线程放弃cpu控制权,并返回tt1线程继续执行直到线程tt1执行完毕
所以结果是tt1线程执行完后,才到主线程执行,相当于在main线程中同步tt1线程,tt1执行完了,main线程才有执行的机会
*/
tt1.join();
tt2.start();
// 我要插队, 等我运行结束之后, 之后的代码才可以开始执行
tt3.start();
线程礼让
static void yield()
对调度程序的一个暗示,即当前线程放弃当前使用的处理器。
public void run() {
for(int i = 0 ; i < 100 ; i++){
// 获取当前线程名称
String sname = Thread.currentThread().getName();
System.out.println(sname+"--"+i);
// 当线程运行到该位置 , 放弃当前cpu的执行权
// 但是CPU 依然有可能还选择你 不能保证均匀
Thread.yield();
}
}
线程终止
void interrupt()
中断这个线程。
// 详解
interrupt() 它基于「一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。」思想,是一个比较温柔的做法,它更类似一个标志位。其实作用不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。
interrupt() 并不能真正的中断线程,这点要谨记。需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,那么就需要这样做:
在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
在调用阻塞方法时正确处理InterruptedException异常。(例如:catch异常后就结束线程。)
1- interrupt 中断操作时,非自身打断需要先检测是否有中断权限,这由jvm的安全机制配置;
如果线程处于sleep, wait, join 等状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常;
2- 如果线程处于I/O阻塞状态,将会抛出ClosedByInterruptException(IOException的子类)异常;
3- 如果线程在Selector上被阻塞,select方法将立即返回;
4- 如果非以上情况,将直接标记 interrupt 状态;
5- 注意:interrupt 操作不会打断所有阻塞,只有上述阻塞情况才在jvm的打断范围内,如处于锁阻塞的线程,不会受 interrupt 中断;
守护线程
public final void setDaemon(boolean on)
将此线程标记为daemon线程或用户线程。
当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。
线程启动前必须调用此方法。
参数 如果 true ,将此线程标记为守护线程
public static void main(String[] args) {
MyThread t1 = new MyThread("皇帝");
MyThread t2 = new MyThread("总管1");
MyThread t3 = new MyThread("总管2");
// 线程启动之前设置 守护线程
t2.setDaemon(true);
t3.setDaemon(true);
// 只要主线程运行完毕, 守护线程 不是立即停止, 等待虚拟机退出
t1.start();
t2.start();
t3.start();
}
Runnable 接口 创建线程
第二种创建线程的方法
查看API Thread 的构造方法
Thread(Runnable target)
分配一个新的 Thread对象。
Thread(Runnable target, String name)
分配一个新的 Thread对象。
// 如何启动一个 Runnable 接口的线程
MyRunnable mr = new MyRunnable();
// 创建线程离不开 Thread
Thread t1 = new Thread(mr,"线程名称1");
Thread t2 = new Thread(mr,"线程名称2");
t1.start();
t2.start();
使用类创建线程 使用接口创建线程的区别
回忆 类和接口的区别
1- 类只能单继承 接口可以多实现
2- 类是什么是什么的概念 接口 什么有什么功能
自己实现一个线程 如果采用继承的方式 , 只能单继承, 不能够再继承其他类
多线程也可以看做是一种功能, 可以实现 业务和逻辑的分离
推荐使用接口来创建线程
线程状态
线程的生命周期
// 如何启动一个 runnable 接口的线程
MyRunnable mr = new MyRunnable();
// 创建线程离不开 Thread
Thread t1 = new Thread(mr,"线程名称1");
Thread t2 = new Thread(mr,"线程名称2");
t1.start();
t2.start();
线程状态图例
线程的5种状态
1- 新建 new 对象
2- 就绪 可运行状态 (对象调用了 start() 方法之后)
3- 运行状态 (CPU 选择了 线程)
4- 死亡 run方法运行完毕
5- 阻塞 由运行状态==> 阻塞状态