网站首页 > 技术文章 正文
java的线程锁有哪些,各自的优劣势。
谈到java的锁,必须提到多线程编程模型:
1.多线程如何进行通信?
1.谈到这个不得不提起JMM(java Memory Model)
堆区以及方法区的内存元素是整个线程共享的。
共享内存机制 (隐式通信,JVM控制的)
2.消息通信机制,wait /notify /notifyall 消息通知 (显式调用)
2.多线程的生命周期
新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态
执行流程分析:
就绪状态(Runnable):使用start()方法启动一个线程后,系统为该线程分配了除CPU外 的所需资源,使该线程处于就绪状态。此外,如果某个线程执行了yield()方法,那么该线程会被暂时剥夺CPU资源,重新进入就绪状态。
运行状态(Running):Java运行系统通过调度选中一个处于就绪状态的线程,使其占有CPU并转为运行状态。此时,系统真正执行线程的run()方法,也只有在运行状态中会去调用java的各种行业去阻塞,锁定,和让出cpu时间片段等操作(yield)
阻塞和唤醒线程
阻塞状态(Blocked):一个正在运行的线程因某些原因不能继续运行时,就进入阻塞 状态。这些原因包括:
a) 当执行了某个线程对象的sleep()等阻塞类型的方法时,该线程对象会被置入一个阻塞集内,等待超时而自动苏醒。
b) 当多个线程试图进入某个同步区域时,没能进入该同步区域的线程会被置入锁定集,直到获得该同步区域的锁,进入就绪状态。
c) 当线程执行了某个对象的wait()方法时,线程会被置入该对象的等待集中,知道执行了该对象的notify()方法wait()/notify()方法的执行要求线程首先获得该对象的锁。
死亡状态(Dead):线程在run()方法执行结束后进入死亡状态,或者执行过程出现异常。
由上图可知,在默认的多线程并发编程模型中,我们分析如果是单核的服务器,多线程其实则会频繁的上下文切换,反而耗时,在多核的开发系统中多线程就是可以在不同核中运行,当线程数大于处理器数时,依然会存在多个线程在同一个CPU上轮换的现象,操作系统都采用抢占式调度策略,对于采用抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务;当该时间段用完后,系统就会剥夺该线程所占用的资源,让其他线程获得执行的机会。在选择下一个线程时,系统会考虑线程的优先级。我们也可以显式的调用java的函数去控制线程的执行顺序。
3.线程操作函数说明:
sleep 和 join 都是可以是线程陷入阻塞状态
控制的都是当前的线程,Thread.sleep() 让当前的线程睡眠
join是让主线程执行子线程有序,join就是主线程必须等待所有子线程执行完,控制顺序一定时
其他线程没有进入就绪状态时候设置。
Thread.yeild方法其实只是给线程调度机制一个暗示:我的任务处理的差不多了,可以让给相同优先级的线程CPU资源了;不过确实只是一个暗示,没有任何机制保证它的建议将被采纳;由于不确定性故也不建议使用.
wait() 与 notify/notifyAll() 是Object类的方法,wait会使线程中断,notify和notifyAll之间的关键区别在于notify()只会唤醒一个线程,而notifyAll方法将唤醒所有线程.
synchronized 关键字底层的原理:
通过javap -c 类名称 (cmd 下查看字节码文件) 可以发现,加了synchronize则会调用过程中有一个监视器monitorenter 去获得锁,结束之后monitorexit去释放锁。
多线程并发带来线程安全问题,故引入了锁机制:
锁的分类:
公平锁/非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。
可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。
对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。
对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。
synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。
独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。
对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。
独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
对于Synchronized而言,当然是独享锁。一个类中的多个synchronized关键字,他们之间是同步的,因为他们锁的对象是一致的,所有方法之间是竞争关系。他们之间是互斥的。
乐观锁/悲观锁
乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。
从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。
悲观锁在Java中的使用,就是利用各种锁。
乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。
偏向锁/轻量级锁/重量级锁
这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
自旋锁
在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
自旋锁和互斥锁比较来理解自旋锁,
自旋锁和互斥锁功在使用时差不多,每一时刻只能有一个执行单元占有锁,而占有锁的单元才能获得临界资源的使用权,从而达到了互斥的目的。自旋锁与互斥锁的区别在于:自旋锁在执行单元在获取锁之前,如果发现有其他执行单元正在占用锁,则会不停的循环判断锁状态,直到锁被释放,期间并不会阻塞自己。由于在等待时不断的"自旋",这也是它为什么叫做自旋锁。所以自旋锁使用时,是非常消耗CPU资源的。而互斥锁在执行单元等待锁释放时,会把自己阻塞并放入到队列中。当锁被释放时,会唤醒队列上执行单元把其放入就绪队列中,并由调度算法进行调度并执行。所以互斥锁使用时会有进程的上下文切换,这可能是非长耗时的一个操作,但是等待锁期间不会浪费CPU资源。所以对两种锁的使用必须要酌情处理。
CAS算法:
java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁。
并发编程中的三个概念:
1.原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
2.可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
3.有序性
有序性:即程序执行的顺序按照代码的先后顺序执行
JVM在执行多线程任务时,共享数据保存在主内存中,每一个线程(执行再不同的处理器)有自己的高速缓存,线程对共享数据进行修改的时候,首先是从主内存拷贝到线程的高速缓存,修改之后,然后从高速缓存再拷贝到主内存。当有多个线程执行这样的操作的时候,会导致共享数据出现不可预期的错误。
解决缓存不一致的问题,有两种解决方案:
- 在总线加锁,即同时只有一个线程能执行i++操作(包括读取、修改等)。
- 通过缓存一致性协议
第一种方法就是类型synchronize,加锁。
第二种就是缓存一致性协议,比如Intel 的MESI协议,它的核心思想就是当某个处理器写变量的数据,如果其他处理器也存在这个变量,会发出信号量通知该处理器高速缓存的数据设置为无效状态。当其他处理需要读取该变量的时候,会让其重新从主内存中读,然后再复制到高速缓存区,volatile 修饰的共享变量是遵循数据一致性原则的。
java.util.concurrent 包是专为 Java并发编程而设计的包的需要研究的东西:
CountDownLatch 部分API
1.CountDownLatch(int count).用给定的count值构造一个CountDownLatch().
2.countDown()方法. 每调用一次该方法,count的计数就会减少1。
3.await()方法。阻塞等待,直到count至0。
使用场景:
1.某一线程在开始运行前等待n个线程执行完毕
2.实现多个线程开始执行任务的最大并行性
多进程之间的锁机制:
多进程之间的锁主要是用中间件来实现分布式锁。
1.数据库唯一索引(使用的是mysql的行级锁)
表级,直接锁定整张表,在你锁定期间,其它进程无法对该表进行写操作。如果你是写锁,则其它进程则读也不允许
行级,,仅对指定的记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
页级,表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
insert和update操作:
InnoDB默认采用行锁,在未使用索引字段查询时升级为表锁
2.redis (使用setnx方法,带返回值的赋值方法,使用redis单线程的特征)
3.zookeeper 使用临时有序节点,保证数据的一致。
- 上一篇: 搞懂这几个锁用法,多线程就懂一半了
- 下一篇: 让我们一起来聊一聊数据库中的锁 数据库里的锁
猜你喜欢
- 2024-10-08 干货总结:彻底搞懂MySQL数据库锁机制(上篇)
- 2024-10-08 慢雾安全团队:代币如果加了锁机制可能会威胁用户资金权限
- 2024-10-08 一文理解MySQL的锁机制与死锁排查
- 2024-10-08 深究用户模式锁的使用场景(异变结构、互锁、旋转锁)
- 2024-10-08 mysql 锁机制介绍 mysql锁原理详细
- 2024-10-08 苹果新增“休眠软件锁”机制,严防更换原装电池
- 2024-10-08 Mysql锁机制 mysql三种锁定机制
- 2024-10-08 Oracle数据库锁机制 oracle数据库 解锁
- 2024-10-08 一文由浅入深带你完全掌握MySQL的锁机制原理与应用
- 2024-10-08 让人闻风丧胆的 Mysql 锁机制 mysql锁机制应用场景
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)