一.概念
一个进程太“重”了,创建一个进程,比较重(消耗的资源比较多,时间也比较长),销毁一个进程也很重
所以线程就是为了解决这个问题,线程要比进程“轻”
创建线程的开销比创建进程小很多,销毁线程的开销也比销毁进程小很多。同时使用多线程,也能解决并发编程的问题
因此使用多线程就可以代替多进程的模型
二.小结
1)进程包含线程,一个进程中,可以有一个或多个线程,但是不能有0个线程
2)进程是资源分配的基本单位,线程是调度执行的基本单位
3)每个进程都有自己独立的资源,一个进程的多个线程之间,共用同一份资源
进程和进程之间是“隔离”的,一个进程出问题不容易影响到其他别的进程
同一个进程的线程和线程之间,是“共享资源”的,好处让线程的创建节省资源申请的开销,销毁也节省资源释放的开销,坏处容易冲突,一个线程出问题也容易把其他线程带走
4)线程也称为“轻量级进程”主要体现在资源申请和释放时开销更低
三.代码
1.继承Thread
这个代码也有一个线程
点击运行创建一个java进程,java进程中就有一个线程,这个线程调用main方法
这个线程也叫主线程
继承Thread由于Thread在Java.long包里就不需要import,并重写run方法
使用线程
结果
多个线程的代码中,线程和线程之间是“并发”的关系,执行逻辑的顺序,谁在前谁在后都有可能(操作系统调度线程的顺序是”随机“的)
如果将myThread.start()改成myThread.run()那么就不会并发执行
2.使用Runnable
3.匿名内部类
此处定义的t并不是指向Thread的实例而是指向了Thread的一个子类
这个子类叫啥名字不知道,匿名的
1.创建Thread的匿名子类
2.重写run方法
3.创建子类的实例,赋值给t引用
使用时需注意这个匿名内部类的逻辑是“一次性的”,另一方面,这里的逻辑最好不要太复杂
4.使用匿名内部类,基于Runnable
用匿名内部类的实例作为参数
5.基于lambda表达式
第一种方法 把线程和线程要执行的任务“耦合在一起”;
第二种方法,Runnable表示“要执行的任务”,Thread表示“线程”,解耦合
第三种方法,匿名内部类
第四种方法,匿名内部类,基于Runnable
第五种方法,lambda表达式
四.两种线程
前台线程:这个线程如果没执行完,此时进程就不会结束,前台线程会阻止进程结束
后台线程:这个线程执行完,还是没执行完都不会影响进程的结束,后台线程不会阻止进程结束
五.Thread的几种属性
1.isDaemon
2.isAlive
描述操作系统内核里是不是有这个线程
六.Thread 类的基本用法
1.创建线程
2.中断线程
让一个线程结束,在Java中让线程结束只能通过使该线程的入口方法尽快return
1.使用变量
2.使用interrupt方法
注,while中需要用isInterrupted 方法判断
当t线程处于阻塞状态(包括不限于sleep)外面通过Interrupt方法中止,就会使t线程触发InterruptedException异常
3.线程等待
线程等待强调线程的结束顺序,使用后结束的线程要等待先结束的线程
join方法
t.join()代表main等待t
当调用t.join的时候要看t线程是否结束,如果t线程仍然在执行,join方法阻塞等待,一直到t线程结束;如果t线程已经结束了,join方法不会阻塞,直接往下执行
join()中可以加入时间,无时间版本就是“死等”,有参数版本是有等待上限,达到上限了即使对方线程还没结束也不等了
4.获取线程引用
Thread.currentThread()的作用是获取到当前对线程对象的引用,相当于this
在哪个线程中调用,就能拿到哪个线程对应的对象的引用
继承Thread时可以用this
5.线程休眠sleep
核心是让线程阻塞
特殊写法
sleep(0)= Thread.yield 虽然不休眠,但是主动放弃了CPU的执行权(有些情况下需要对每个线程消耗的CPU资源做限制)