首页
Web开发
Windows程序
编程语言
数据库
移动开发
系统相关
微信
其他好文
会员
首页
>
编程语言
> 详细
多线程基础
时间:
2019-01-19 14:28:49
阅读:
194
评论:
0
收藏:
0
[点我收藏+]
标签:
LEDE
时间段
int
管理
不能
利用
node
开发
并且
一、CPU时间分片实现
CPU只管干活,是操作系统实现的纳秒级时间分片
二、并发和并行
核心区别在于进程是否同时执行,如KTV话筒,
并行指的是有多少人可以使用话筒同时唱歌
并发指的是同一个话筒被多人轮流使用
并发
指在某个时间段内,多任务交替处理的能力
CPU把可执行时间均匀的分成若干份,轮流抢占使用
并行
指同时处理多任务的能力。
目前CPU已经发展为多核,可以同时执行多个互不依赖的指令及执行块。
三、并发环境下,由于程序的封闭性被打破,出现了以下特点
并发程序之间有相互制约的关系
直接制约,一个程序等待另一个程序的计算结果
间接制约,多个程序竞争共享资源,如处理器、缓冲区等
并发程序的执行过程是断断续续的
当并发数设置合理并且CPU拥有足够的处理能力时,并发会提高程序的运行效率
四、线程
CPU调度和分派的基本单位
多线程的作用是提高任务的平均执行速度
效率最大化,合适的线程数才能让CPU资源被充分利用(太多容易造成通道拥堵)
线程可以拥有自己的操作栈、程序计数器、局部变量表等资源
线程与同一进程内的其它线程共享该进程的所有资源
线程在生命周期内存在多种状态
NEW:新建状态
线程被创建且未启动的状态
创建线程的三种方式
继承自Thread类
实现Runnable接口
推荐方式,因为继承自Thread类不符合里氏替换原则,而实现Runnable接口可以编程更加灵活,对外暴漏的细节更少,让使用者专注于线程的run()方法上。
通过setDefaultUncaughtExceptionHandler()在主线程中捕获到子线程异常
实现Callable接口
可以通过call()获得返回值
call()可以抛出异常
RUNNABLE:就绪状态
调用start()之后运行之前的状态(多次调用start抛出IllegalStateException)
RUNNING:运行状态
run()正在执行时线程的状态
线程会由于某些因素而退出RUNNING,如时间、异常、锁、调度等
BLOCKED:阻塞状态,进入此状态,有以下几种情况
同步阻塞:锁被其它线程占用
主动阻塞:调用Thread的某些方法,主动让出CPU执行权,比如sleep()、join()等
等待阻塞:执行了wait()
DEAD:终止状态
run()执行结束,或因异常退出后的状态
此状态不可逆转
五、保证高并发场景下的安全,可以从以下四个维度考量
数据单线程内可见
单线程总是安全的
通过限制数据仅在单线程内可见,可以避免数据被其他线程篡改
最典型的就是线程局部变量,它存储在独立的虚拟机栈帧的局部变量表中
ThreadLocal采用这种方式来实现线程安全的
只读对象
只读对象总是安全的
允许复制、拒绝写入
最典型的只读对象就是String、Integer等
一个对象想要拒绝任何写入,必须要满足以下条件
使用final关键字修饰类,避免被继承
使用private final关键字避免属性被中途修改
返回值不能是可变对象的引用
线程安全类
如StringBuffer
同步与锁机制
开发工程师在代码中实现安全的同步机制
六、java.util.concurrent,JUC
Java并发包中大多数类注释都写有:
@author Doug Lea
Doug Lea在大学当老师时,专攻并发编程和并发数据结构设计,主导设计了JUC并发包,提高了Java并发编程的易用性,大大推进了Java的商用进程
并发包主要分成以下几个类族
线程同步类
这些类使线程间的协调更加容易,支持了丰富的线程协调场景
逐步淘汰了使用Object的wait()和notigy()进行同步的方式
主要代表
CountDownLatch
倒计时器
时间维度,基于执行时间的同步类
用完后只能重新创建一个新的再使用
Semaphore
基于信号维度
permits定义为1时,就是互斥锁
permits定义为0时,就是共享锁
CyclicBarrier
基于ReentrantLock实现的
可以循环使用的屏障式多线程协作方式
批量执行
设置信号窗口为3,3个线程同时执行,只有3个线程同时都执行完,才会放下一批进入。
通过CyclicBarrier的reset释放线程资源
其它,如StampedLock、ReentrantReadWriteLock
并发集合类
集合并发操作的要求是执行速度快、提取数据准
最著名的非ConcurrentHashMap莫属,不断优化,由刚开始的锁分段到后来的CAS,不断的提升并发性能
其它还有ConcurrentSkipList、CopyOnWriteArrayList、BlockingQueue等
线程管理类
把Thread发扬光大的是线程池
使用Executors静态工厂或者ThreadPoolExecutor等
通过ScheduledExecutorService执行定时任务
锁相关类
以Lock接口为核心,派生出在一些实际场景中进行互斥操作的锁相关类
最有名的是ReentrantLock
七、什么是锁?
从悲观锁,发展到乐观锁、偏向锁、分段锁
特性
互斥性
不可见性
因为锁的存在,某些操作对外界来说是黑箱进行的,只有锁的持有者才知道对变量进行了什么修改
Java中常用锁实现的方式
用并发包中的锁类
Lock接口,实现逻辑并未用到synchronized,而是利用了volatile的可见性
ReentrantLock
可重入锁
对于Lock接口的实现主要依赖了Sync
Sync继承了AbstractQueuedSynchronizer(AQS),是JUC包实现同步的基础工具
在AQS中,定义了一个volatile int state变量作为共享资源,如果线程获取资源失败,则进入同步FIFO队列中等待;如果成功获取资源就执行临界区代码,执行完释放资源时,会通知同步队列中的等待线程来获取资源后出队并执行。
AQS是抽象类,内置自旋锁实现的同步队列,封装入队和出队的操作,提供独占、共享、中断等特性的方法。
公平锁、非公平锁
利用同步代码块
一般使用synchronized关键字,非公平锁、悲观锁
加锁方式
在方法签名处加synchronized关键字
使用synchronized(对象或类)
加锁原则
锁的范围尽可能的小,即能锁对象,就不要锁类
锁的时间尽可能的短,即能锁代码块,就不要锁方法
synchronized锁特性由JVM负责实现
JVM底层是通过监视锁来实现synchronized同步的,监视锁,即monitor,是每个对象与生俱来的一个隐藏字段。monitor非0,其它线程就会进入阻塞状态。
通过字节码学习synchronized锁是如何实现的
JDK6版本后,synchronized提供三种锁实现(也提供自动升级和降级机制)
偏向锁
JVM利用CAS在对象头上设置线程ID,表示这个对象偏向于当前线程,这就是偏向锁。
可以降低无竞争开销,不是互斥锁,不存在线程竞争的情况,省区再次判断的步骤,提升了性能。
为了在资源没有被多线程竞争的情况下尽量减少锁带来的性能开销。在锁对象的对象头中有一个ThreadId字段,当第一个线程访问锁时,如果该锁没有被其它锁访问过,即ThreadId字段为空,那么JVM让其持有偏向锁,并将ThreadId字段的值设置为该线程的ID。当下一次获取锁时,会判断当前线程的ID是否于锁对象的ThreadId一致。如果一致,那么该线程不会再重复获取锁,从而提高了程序运行效率。
轻量级锁
如果出现锁竞争情况,那么偏向锁会被撤销并升级为轻量级锁
重量级锁
如果资源的竞争非常激烈,会升级为重量级锁
八、线程同步
原子性
指不可分割的一系列操作指令,在执行完毕前不会被任何其它操作中断,要么全部执行,要么全部不执行。
典型的如i++或++i操作,看似原子性,实际不是,需要分三步
ILOAD->IINC->ISTORE
更加复杂的CAS具备原子性
不会出现线程的上下文切换
线程同步
就是线程之间按某种机制协调先后次序执行,当有一个线程对内存操作时,其它线程都不可以对这个内存地址进行操作
实现线程同步的方式
同步方法
锁
阻塞队列
其它
关键字volatile
解决的是多线程共享变量的可见性问题,但不具备互斥性。
对volatile变量的操作并非都具有原子性
只是轻量级的线程操作可见方式,并非同步方式
适合一写多读的场景,最典型的应用是CopyOnWriteArrayList
所有的操作都需要同步给内存变量,因此volatile一定会使线程的执行速度变慢。
线程的切换和执行都是纳秒级的
指令优化(指令重排)
效率最大化处理
计算机并不会根据代码顺序按部就班的执行相关指令
CPU在处理信息时进行指令优化
哪些取数据动作可以合并进行
哪些存数据动作可以合并进行
happen before
时钟顺序的先后,并不能保证线程交互的可见性
可见性
指某线程修改共享变量的指令对其他线程来说都是可见的
它反映的是指令执行的实时透明度
线程本地内存保存了引用变量在堆内存中的副本,线程对变量的所有操作都在本地内存区域中进行,执行结束后再同步到堆内存中去。在这个时间差内,该线程对副本的操作,对于其它线程都是不可见的。
使用volatile修饰变量
意味着任何对此变量的操作都会在内存中进行,不会产生副本,以保证共享变量的可见性,局部阻止了指令重排的发生。
著名的双重检查锁定(Double-checked Locking)问题
对象引用在没有同步的情况下进行读操作,导致用户可能会获取未构造完成的对象。
使用单例设计模式时,即使用双检锁也不一定会拿到最新的数据。
与Java虚拟机的优化有关
对Java编译器而言,初始化某个类类型的实例和将对象地址写到成员属性字段并非原子操作,且这两个阶段的执行顺序是未定义的。
一种较为简单的解决方案
使用volatile关键字修饰目标属性(适用于JDK5及以上版本)
限制了编译器对它的相关读写操作,对它的读写操作进行指令重排,确定对象实例化之后才返回引用。
锁也可以确保变量的可见性
线程得到锁时读入副本,释放时写回内存
锁的操作尤其要符合happen before原则
信号量同步
在不同的线程之间,通过传递同步信号量来协调线程执行的先后顺序。
Semaphore信号同步类
Semaphore semaphore = new Semaphore(3);//3个服务窗口
只有再调用Semaphore对象的acquire()成功后,才可以往下执行,完成后执行release()释放持有的信号量,下一个线程就可以马上获取这个空闲的信号量进入执行
Semaphore的窗口信号量等于1,就是典型的互斥锁
尽量使用JUC包提供的信号同步类,避免使用对象的wait()和notigy()方式来进行同步
多线程基础
标签:
LEDE
时间段
int
管理
不能
利用
node
开发
并且
原文地址:https://www.cnblogs.com/bee4j/p/10291470.html
踩
(
0
)
赞
(
0
)
举报
评论
一句话评论(
0
)
登录后才能评论!
分享档案
更多>
2021年07月29日 (22)
2021年07月28日 (40)
2021年07月27日 (32)
2021年07月26日 (79)
2021年07月23日 (29)
2021年07月22日 (30)
2021年07月21日 (42)
2021年07月20日 (16)
2021年07月19日 (90)
2021年07月16日 (35)
周排行
更多
Spring Cloud 从入门到精通(一)Nacos 服务中心初探
2021-07-29
基础的排序算法
2021-07-29
SpringBoot|常用配置介绍
2021-07-29
关于 .NET 与 JAVA 在 JIT 编译上的一些差异
2021-07-29
C语言常用函数-toupper()将字符转换为大写英文字母函数
2021-07-29
《手把手教你》系列技巧篇(十)-java+ selenium自动化测试-元素定位大法之By class name(详细教程)
2021-07-28
4-1 YAML配置文件 注入 JavaBean中
2021-07-28
【python】 用来将对象持久化的 pickle 模块
2021-07-28
马拉车算法
2021-07-28
用Python进行冒泡排序
2021-07-28
友情链接
兰亭集智
国之画
百度统计
站长统计
阿里云
chrome插件
新版天听网
关于我们
-
联系我们
-
留言反馈
© 2014
mamicode.com
版权所有 联系我们:gaon5@hotmail.com
迷上了代码!