1.线程介绍
线程是操作系统能够运行运算调度的最小单位,它被包含在进程中,是进程中的实际运作单位。
并行、异步
线程的意义:
在多核CPU中,利用多线程可以实现真正意义上的并行执行。
进程阻塞会引起不依赖该任务的进程也被阻塞。通过对不同任务创建不同的线程去处理,可以提升程序处理的实时性。
线程是轻量级的进程,线程的创建、销毁比进程更快。
2.线程的应用场景
1.使用多线程实现文件下载
2.后台任务:如向大量用户发送邮件
3.异步处理:记录日志
4.多步骤的任务处理,可根据步骤特征选用不同个数和特征的线程来协作处理,由一个主线程分割成多个线程完成。
本质是合理的利用多核芯CPU资源来实现线程的并行处理,来实现同一个进程内的多个任务的并行执行,同时基于线程本身的异步执行特征,提升任务处理的效率。
3.线程的创建
1.继承Thread类
重写Run()方法,实例化后使用start()方法运行。
继承的是类。
2.实现Runnable接口
类实现Runnable接口,重写run()方法,因为接口没有实现方法所以使用Thread的start()方法,传入类的参数,最后使用start()方法。
实现的是接口。
3.实现Callable接口、Futuer
类实现Callable接口带有类型,自带返回值。运用线程池进行,实例化一个线程池,通过submit()方法接受类的信息,使用future拿到信息。最后用future.get方法得到。
存在一个返回值
4.线程池
PS: Thread的run()和start()方法的区别
Thread的start方法开启了新线程,并在线程中执行了run方法,而run则只是在当前线程中执行了其构造函数中传入的Runnable对象的run方法。
start()能够异步调用run()方法,直接调用run()方法是同步的,要实现多线程的目的只有使用start()方法。
start()线程的过程
4.线程的生命周期
1.初始状态:
New,线程被构建,但还没有调用start方法
2.运行状态:
Runnable,就绪和运行两种状态都称为“运行中”
3.阻塞状态:
Blocked,线程进入等待状态,某种原因线程放弃了CPU的使用权
4.等待状态:
Waiting,等待。不带时间
5.超时等待状态:
Time_Waiting,超时等待,超时以后自动返回。带有超时时间的等待。
6.终止状态:
Terminated,当前线程执行完毕
Runnable(准备) -> Waiting: object.wait()、object.join()、locksupport.park()
Waiting -> Runnable(运行): object.notify()、object.notifyAll()、locksupport.unpark(Thread)
Runnable(准备) -> Timed_Waiting: object.wait(long)、thread.sleep(long)、thread.join(long)、locksupport.parkNanos()、locksupport.arkUntil()
Time_Waiting -> Runnable(运行):object.notify()、object.notifyAll()、locksupport.unpark()
Blocked状态只有进入synchronize方法或代码块
查看线程 jdc
5.线程的基本操作
1.Thread.join
是保证线程执行结果的可见性,也就是说会保证使用join方法的线程优先执行。
2.Thread.sleep
线程暂停执行,直到等待的时间结束才恢复执行或在这段时间内被中断。
3.Thread.yield
使当前线程从执行状态(运行状态)变为可执行态(就绪状态),就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行。
工作流程:
1.挂起线程修改运行状态
2.sleep()方法设定定时器
3.结束时触发定时器,内核修改运行状态。
PS:Thread.sleep(0)的意义,进行CPU的竞争,使CPU重新分配资源。
3.Thread.wait和Thread.notify
使用它们可以解决:生产者消费者模式,wait会释放锁。
PS:为什么要加synchronized关键字 wait和notify方法是互斥存在的,本质是一种条件的竞争,所以需要加。
4.关闭线程的方式
线程友好结束的条件是Run方法中的方法结束,即run()方法结束后线程就会被终止。
Thread.stop()方法不建议使用,可能会导致数据不完整,运行不完整。
1.使用共享变量进行数据的交互,Thread.currentThread().isInterrupter()方法
2.Thread.interrupt()方法 通知线程中断,把共享变量从false设置为true
3.Thread.interrupted()方法,获取标志位并且手动复位
5.线程的复位
jvm进行复位
手动、被动复位
6.线程安全问题
线程安全:当多个线程访问某个对象时,不管运行时环境采用何种调度方式活着这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类是线程安全的。
线程的三大特性:原子性、可见性、有序性
如何解决可见性有序性问题
本质是存在缓存和指令重排序的问题。
加锁,本质是共享了资源。
Volatile、synchronized、final关键字
1.synchronized
范围:对于普通同步方法,锁是当前实例对象
对于静态同步方法,锁是当前类的Class对象
对于同步方法块,锁是Synchonized括号里配置的对象
实例锁(对象实例)、类锁(静态方法、类对象)、代码块锁
锁的储存:
锁的升级:jdk1.6版本后
无锁-》 偏向锁-》轻量级锁(cas自旋)-》重量级锁
偏向锁无法存储hashcode
如何打印锁的信息
2.volatile关键字
共享数据在多线程情况下保证可见性,解决可见性和有序性的问题 实质是 #Lock指令禁止高速缓存
Store Barried、LoadBarrier、Full Barrier(读、写、复合)
指令重排序问题可用读写屏障解决
其实质:
在CPU层面上,存在高速缓存 会导致数据一致性的问题。解决:[总线锁]-》[缓存锁]
缓存锁:缓存一致性协议
MSI、MESI、MOSI....
Modify(修改) 只缓存在当前Cpu与主内存数据不一致、Exdusive 独享,数据只存在在当前Cpu并且没有被修改、Shared(分享) 数据存在多个Cpu,与主内存一致、Invalid(失效)数据在这个Cpu是失效的。
优化缓存一致性协议:
异步,等待或通知时,继续执行。等待或失效完成后,再更新。
Store Bufferes + Invalid Queue,但会出现指令重排序问题
内存屏障指令:
使得Store Bufferes + Invalid Queue失效
3 final关键字
提供了内存屏障的规则
2.Happens-Before原则
前面一个操作对后续操作是可见的。
1.程序顺序规则
2.监视器锁规则
3.Volatile变量规则
4.传递性规则
5.start()规则
6.Join()规则