Java多线程的基本知识

旧时模样 提交于 2020-03-06 18:54:17

 1、进程与线程

进程是指一个程序的执行过程,持有资源和线程

线程是系统中最小的执行单元,一个进程可以有多个线程,线程共享进程资源,具有同步(线程的协作)与互斥(资源的争抢)

例如:我们一个班级当做一个进程,班级里面的学生就是线程,里面的学习工具就是资源,学生们的相互协作与竞争就是线程之间的同步与互斥

 2、java对线程的支持

Thead类及Runnable接口

Runnable用来定义任务   Thead控制任务执行

 Runnable可以避免Thread方式由于java单继承的特性带来的缺陷,可以被多个线程(Thread实例)共享,适合于多个线程处理同一个资源的情况。

class MyThread extends Thread{
    @Override
    public void run() {
        
    }
}
MyThread mt = new MyThread();
mt.start();lass MyThread implements Runnable {
    public void run() {    }}MyThread myThread = new MyThread();Thread td = new Thread(myThread);td.start();
 

 

 

3、线程的生命周期

 

其中阻塞事件包括:sleep、 yield、 join、wait

阻塞解除:等待或休眠时间到了  notify notifyAll

 

用户线程:运行在前台,执行具体的任务

守护线程:运行在后台,为其他前台线程服务 当所有的用户线程都结束,守护线程会随jvm一起结束

应用:数据库连接池中的监测线程、jvm虚拟机启动后的监测线程、  垃圾回收线程

Thread.setDaemon(true)将一个线程设置为守护线程,在start()调用前设置,否则会抛异常在守护线程中产生的新线程也是守护线程不是所有的任务都可以分配给守护线程执行,比如:读写操作或计算逻辑

jstack是java虚拟机自带的一种堆栈跟踪工具,作用是生成jvm当前时刻线程的快照,帮助定位程序出现问题的原因

 

线程的终止:线程的run方法执行完成是线程自动终止,线程的stop会使线程突然停止,导致业务流程中断,影响数据库事务的控制及资源的清理工作,正确的停止方法是使用退出标志。

interrupt方法为Thread内部实现的一个中断线程的标志,isInterrupt方法可以判断当前线程是否有中断标志,但线程中断后,如果当前线程调用了线程的阻塞方法,将会抛出一个中断异常,且中断标志会被清除,导致线程无法停止;
中断只是暂停线程的执行,保留运行环境,让cpu能去执行相应的io中断程序,之后再回来继续执行。

4、线程之间的交互

争用条件:当多个线程同时共享一份数据的时候,如果每个线程都尝试操作数据,会使数据被破坏,从而先形成争用条件

互斥:synchronized 通过锁的概念同一时间只有一个线程获取操作权限

同步:

Object.wait 是线程等待释放锁

Object.notify 唤起随机唤醒某一个等待线程

Object.notifyAll 唤起其他所有等待线程

5、线程的可见性

可见性:一个线程对共享变量值的修改,能及时的被其他线程看到;

共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。

指令重排:jvm会在编译代码生成程序指令的时候,为了达到对执行效率进行优化的目的,会对代码中指令的顺序进行重排,当必须满足单线程中指令重排后的执行结果不会发生变化

在进程中所有的变量都存在堆内存中,共所有的线程共享,每个线程有自己的栈内存,相互独立,线程中工作内存的变量的传递只能通过主内存来完成。


 


 想要共享变量对所有的线程可见,必须满足:

  • 线程工作内存中更新共享变量必须及时刷新到主内存中
  • 主内存的最新的共享变量能及时更新到线程的工作内存中

 

synchronized实现原理:
线程解锁前,必须把共享变量的最新值刷新到主内存中
线程加锁前,会清空工作内存中的共享变量的值,从而使用共享变量时需要从主内存中读取最新的值(注意加锁和解锁需要是同一把锁)

volatile 实现原理:
当一个变量被volatile修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据

特性:保证变量在线程之间的可见性;阻止编译时和运行时的指令重排

具体实现:

1.在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障。

2.在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障。

内存屏障共分为四种类型:

LoadLoad屏障:

抽象场景:Load1; LoadLoad; Load2

Load1 和 Load2 代表两条读取指令。在Load2要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障:

抽象场景:Store1; StoreStore; Store2

Store1 和 Store2代表两条写入指令。在Store2写入执行前,保证Store1的写入操作对其它处理器可见

LoadStore屏障:

抽象场景:Load1; LoadStore; Store2

在Store2被写入前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障:

抽象场景:Store1; StoreLoad; Load2

在Load2读取操作执行前,保证Store1的写入对所有处理器可见。StoreLoad屏障的开销是四种屏障中最大的。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!