Java多线程
2023-11-12 09:37:20 0 举报
AI智能生成
为你推荐
查看更多
自己总结 可能不全
作者其他创作
大纲/内容
font color=\"#e65100\
外框
线程状态
线程操作
start()用于启动线程,run()是线程启动后执行的代码入口
start()和run()
区别:1.使用thread开启线程 可以在子类中直接调用父类的方法(例如线程名称:属性等) 2.(1)使用runnable如果想拿到当前线程的属性方法 则必须先拿到线程的实例(Thread.currentThread()) (2)使用runnable创建的类并不是线程类 而是线程类执行的target的目标类 需要将实力传入线程类 也就是(Thread)的构造器才能创建并且执行线程 (3)runnable可以更好实现多个线程并发访问的完成同一个任务或资源thread和runnable都不能支持返回值
thread和runnable
问题:Callable能否和runnable一样,作为Thread线程的实例的target来执行? 不行 因为Thread中的Target属性为runnable callable接口和runnable并 没有任何继承关系
callable和futureTask
1.object.wait()(等待),object.notify()唤醒 现成等待和唤醒 需要统一对象锁来开启 要不然不知道是哪个等待或者唤醒哪个 使用该方法的时候 必须在拥有对象的同步锁情况下进行 wati()流程:1.调用改方法后将该线程加入 waitset集中 等待被唤醒 2.释放当前owner权利 让当前线程进入waiting notify() 1.唤醒waitset()中第一条等待的线程 如果是notifyAll()会唤醒所有的等待线程 2.唤醒的线程会从waitSet()移动到EntryList中 让线程具有抢夺owner的权利 线程的状态从 waiting 变成blocked 3.entryList中的线程抢夺到owner的权利后 线程的状态从blocked变成runnable
等待-通知模式
线程之间的通信
1.int corePoolSize 线程核心数量 也就是最少线程数量2.int maxmumPoolSize 最大线程数量 线程数量大于corepoolSize 小于maximunpoolSize并且阻塞队列已满时才会创建新线程3.long keepAliveTime 空闲线程存活时间 3.阻塞对垒BlockingQUeue<Runnable> 4.新线程产生方式ThreadFactory 5.拒绝策略 RejectedExecutionHanler (1)AbortPolicy 拒绝策略 线程池队列满了 新任务就会被拒绝 并且抛出异常 也是线程池默认策略 (2)DiscardPolicy 抛弃策略 线程池队列满 新任务会被抛弃 不会抛出异常 (3)DiscardOldestPolicy 抛弃最老任务 现成队列满了 就会将最早进入队列的任务抛弃 (4)CallerRunsPolicy 执行者策略 新任务添加时 如果添加失败 那么提交任务线程会自己去执行该任务 不在线程池中执行如何设置核心线程数量 1.IO密集任务 io操作的多 一般是核心cpu核心数的两倍 2.CPU密集任务 主要是大量的计算 等于cpu的核心数 这样才效率高 不频繁切换上下文 3.混合型任务 最佳线程数 = ((线程等待时间+线程CPU时间)/线程cpu时间)+CPU核数
核心参数
ThreadPoolExecutor线程池
线程
线程本地副本 1,主要作用线程隔离,夸函数传递 能够实现每个线程都有一份变量的本地值,每个线程都有自己的独立ThreadlocaMap空间 key为threadlocal实例 value 为保存的值 也就是threadloca中使用map保存了不同线程的值而已 然后根据不同线程来取不同的值会存在内存泄露情况 threadLoca中的key是弱引用 在gc回收的时候key被回收 但是value不会回收 存储的数据多了就容易内存泄露 解决方法就是调用remove()方法
threadLocal经典面试
我觉得这个文章面试总结的挺他么的到位 nice
ThreadLocal面试问题
ThreadLocal
并发
1. i++ 不是线程安全操作 因为它是一个复合操作 其中包含三个jvm指令:内存取值,寄存器增加1,存值到内存(这三个都是原子性操作,但是两个以上的院 原子操作一起进行的就不是原子操作了) 这三个指令都是独立运行的 中间完全可能会出现多个线程并发执行 例如:4个线程同时读取到count=1 并且都进行自增操作 然后存到内存 结果count=2 并不是count=42.object.wait()和notify()方法为什么要在synchronized同步代码块中使用 wait因为jvm释放当前对象锁监视器的owner(使用权) jvm会将当前线程移入到WaitSet()队列 这些操作都是和对象监控器相关的 notify jvm对象锁的waitset() 移动到entrylist也都是跟对象锁监视器有关的 什么是对象监视器:例如 : Object obj= new Object() synchronized(obj){} 这个obj就是对象监视器3.为什么Java局部变量 方法参数不存在内存可见性问题 在Java中 所有的局部变量和方法参数都不会线程共享 所以也不存在内存可见性问题 所有的object 实例 class实例和数组都存在jvm 堆内存 堆内存存在线程共享 所以有线程可见性问题
面试可能会问的问题
因为Java字节码运行在JVM中,而JVM运行在各个操作系统上,所以当JVM想要进行线程创建和回收的这种操作时,是必须要调用操作系统的相关接口,也就是说JVM线程与操作系统线程之间存在着某种映射关系。这两种不同维度的线程之间的规范和协议呢,就是线程模型
概念
用户线程与内核线程建立了一对一的关系缺点:如果用户线程阻塞会直接反映到操作系统上 导致内核状态频繁切换降低性能
一对一
多个用户线程映射到一个内核线程上 用户线程的调度是用户空间来完成的缺点:如果一个用户线程进行了内核的调用并阻塞,其他线程都无法进行内核调用,Java早期就用了这种模型
多对一
用户模型和内核线程是多对对
多对多
模型分类
线程主要分两类 内核线程,简称KLT(Kernel Level Thread) 用户线程,简称ULT(User Level Thread)
名词解说
Java线程模型
1.红色框为线程私有的 是线程安全的 不用考虑2.蓝色框 Java堆(主要存的对象) 和方法区(主要存类信息,常量和静态变量等)都是线程共享的 所以要考虑数据安全问题
了解锁的第一步先了解jvm内存模型
对象头包含了两个部分: mark work class point 其中class point就是一个指针指向了当前对象 类型所在方法区中的类型数据 mark word存储了运行时的数据 例如:锁标志信息 hashcode 指向锁记录的指针等
1.对象头主要存放的是运行时的信息 2.实例数据存放的主要是 对象的属性 方法
首先看一下对象结构
对象头结构
每个object都拥有一把锁,这把锁存在对象头中,锁中记录了当前对象被那一个线程所占用也就是这个锁记录在对象头的mark work中
代码层面是如何实现的
锁是怎么设计的
1.entry set中聚集了很多想要进入monitor的线程 他们处于waiting状态2.例如A线程进入了monitor 那他的状态就是active状态 假如A线程经过了一个判断 需要让出执行全 它会被放到wait set中等待 状态标记成waiting 此时B线程成功进入monitor 并且执行完成后 可以手动唤醒 (notify)wait set中的A线程 来继续执行synchronized被编译后会生成monitorenter monitorexit 两个字节码对业务代码进行包裹指令来进行线程同步缺点:可能存在性能问题 java线程实际上是操作系统的映射 所以每次挂起或者唤醒是比较费时间的
工作流程
修饰静态方法:锁住当前 class,作用于该 class 的所有实例修饰非静态方法:只会锁住当前 class 的实例修饰代码块:该方法接受一个对象作为参数,锁住的即该对象
使用
1.无竞争 所有线程都能访问到同一个资源2.存在竞争 但是想使用无锁的方式同步线程 就是不通过锁定资源的方式来保证现成安全同步 可使用CAS
无锁
一段同步代码,一直被A线程访问没有其他的线程来竞争 这种情况下就会进入偏向锁的状态,让对象认知这个线程 对象直接把所得使用权交给它也就是说偏向锁只有一次CAS操作偏向锁是如何实现的:1.查看Mark Word中偏向锁的标识以及锁标志位,若是否为偏向锁为1(倒数第三个数字),并且锁标志位为01(最后倒数两位是0 v 1),则该锁为可偏向状态。 2.若该锁为可偏向状态,判断Mark Word中的线程ID(前23个bit)与当前线程ID是否相等,如果相同,则直接执行同步代码,否则通 过CAS操作竞争锁。 3.如果竞争成功,将Mark Word中线程ID设置为当前线程ID,然后执行同步代码。 4.如果竞争失败,说明有其他线程竞争。持有偏向锁状态的线程在没有字节码正在执行的情况下释放锁,然后恢复到未锁定状态或者膨 胀为轻量级锁。 释放偏向锁的过程: 只有遇到其他线程尝试竞争偏向锁时,持有偏向锁状态的线程才会释放锁。 持有持有偏向锁的线程需要等到所有的同步任务执行完成之后(即没有字节码正在执行),才会暂停持有偏向锁的线程,然后恢复到未锁 定状态或者膨胀为轻量级锁。
偏向锁
当有两个线程开始竞争这个锁对象,情况发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象,锁对象的 Mark Word 就指向哪个线程的栈帧中的锁记录当一个线程想要获取一个对象的锁时 判断锁标志位如果是00的话 则认为是轻量级锁这时线程会在虚拟机开辟一块叫做 lock record的空间 来存储mark word的副本 和owner指针线程通过CAS尝试获取锁 如果获取成功则复制 对象头中的mark work到lock record中 并且将要owner指针指向该对象对象中的前30个bit 会生成一个指针 指向线程虚拟机中的lock record 这样就实现了对象锁和线程的绑定然后获取了这个线程的对象就会去执行一些任务,如果有其他线程想要获取该对象的话 只能是自旋等待也就是自旋超过阈值了就会变成重量级锁
轻量级锁
轻量级锁自旋超过阈值的时候 就升级成了重量级锁 重量级锁主要是就是monitor来控制线程
重量级锁
重量级锁(互斥锁)
什么是栈帧
在自旋状态下,当一个线程A尝试进入同步代码块,但是当前的锁已经被线程B占有时,线程A不进入阻塞状态,而是不停的空转,等待线程B释放锁。如果锁的线程能在很短时间内释放资源,那么等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞状态,只需自旋,等持有锁的线程释放后即可立即获取锁,避免了用户线程和内核的切换消耗。
自旋
由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定:如果在同一个锁对象上,自旋等待之前成功获得过的锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,因此允许自旋等待持续相对更长的时间。相反的,如果对于某个锁,自旋很少成功获得过,那么以后要获取这个锁时将可能减少自旋时间甚至省略自旋过程,以避免浪费处理器资源。自适应自旋解决的是“锁竞争时间不确定”的问题。JVM很难感知确切的锁竞争时间,而交给用户分析就违反了JVM的设计初衷。自适应自旋假定不同线程持有同一个锁对象的时间基本相当,竞争程度趋于稳定。因此,可以根据上一次自旋的时间与结果调整下一次自旋的时间。
适应性自旋
CAS是cpu指令级的原子操作 unsafe提供了CAS方法直接通过native方式调用了CPU指令cmpxchg(所以它没有具体的实现方法 而是底层调用了c) 这一条指令具备原子性 所以不会造成数据不一致的问题什么他么的是原子性:不可再分割的最小操作 在执行完毕之前不会受到其他任务或事件影响而中断的操作什么是偏移量(offset):例如 atomicinteger中的value就是偏移量 就是对象地址+value地址就是偏移量CAS就是Compare and Swap,是项a href=\"https://so.csdn.net/so/search?q=%E4%B9%90%E8%A7%82%E9%94%81&spm=1001.2101.3001.7020\" target=\"_blank\" class=\"hl hl-1\" data-report-click=\
CAS
拓展CAS
锁的升级
synchronized
不可中断的一个或者一系列操作,这种操作一旦开始就一直到结束中间不会有任何线程切换
原子性
是指代码执行的先后顺序
有序性
volatile关键字可以实现 这个关键字可以保证可见性 和有序性(因为禁止指令重排序) 每一个线程都有自己的工作内存,当线程使用变量的时候 会把主内存的变量复制到工作内存中,读写操作都是在工作内存中的变量副本,操作完成后 将工 作内存的数据刷回主线程,保证线程的可见性 有点逼格的原理概述:当一个变量被volatile修饰的时候 汇编后 会在操作变量前多了一个lock addl指令 这个指令有三个意思:1 将当前cpu缓存的数据立即写会系统内存 2.会引起其他cpu缓存该地址数据无效 3.禁止指令重排序(也就是保证了有序性 valatile是如何保证有序性的:在volatile读操作插入一个 loadload屏障 在 写操作前后插入一个storestore屏障 )
可见性
cpu为了提高运行效率,编译器和cpu常常会对指令进行重排序 它不保证各个语句执行顺序和代码中的先后顺序一致,但是它最终保证程序的执行结果和代码 结果是一致的 as-if-serial 编译器和cpu指令重排序都要遵守as-if-serial规则 as-if-serial:无论如何重排序,都必须保证代码在单 线程下运行正确,不会对存在数据依赖进行重排序 例如:int a = 1 ; int b = 2; i nt c=a+b; c和a c和b都有数据依赖关系 在指令重 排序c不能排到a和b的前面 可能会改变程序的结构 但是a和b可以重排序Java中禁止指令重排序的有 volatile synchronized final等
指令重排序
Java高并发三大特性
jmm定义了一组规范,一个线程对共享变量写入时 如何确保对另外一个线程是可见的 实际上jmm提供了河里的禁用缓存以及禁止重排序,所以其核心价值在于解决了可见性和有序性拼比各种已硬件和操作系统访问差异保证Java程序在各种平台对内存访问最终一致jmm的两个概念:1.主存 2.工作内存jmm的规定:1.所有变量储存在主存中 2. 每个线程都有自己的工作内存,对变量的操作都是在工作内存中进行的 3.不同线程之间无法访问彼此的工作内存,要想访问只能通过主存来传递jmm的8个操作:
JMM(内存模型)
LOCK(锁)接口
Java显示锁
java锁
Java多线程
0 条评论
回复 删除
下一页