Java中的 您所在的位置:网站首页 java多个进程怎么读取同一张表数据 Java中的

Java中的

2023-03-22 15:49| 来源: 网络整理| 查看: 265

java中有很多锁相关的概念,这些锁之间有着千丝万缕的关系,希望通过这篇文章让你对这些锁有个初步的认识,对了,这篇文章3500多字,但其实只是锁相关知识的的目录。

java中有哪些锁相关的概念?

无锁,偏向锁,轻量级锁,重量级锁

自旋锁

重入锁,不可重入锁

读写锁,互斥锁,独占锁,共享锁

乐观锁,悲观锁

公平锁,非公平锁

分段锁

锁膨胀

锁消除

锁粗化

分布式锁

无锁

无锁就是没有锁,相对比较好理解。

偏向锁

这个锁跟synchronized有关系,当锁对象第一次被线程A获取的时候,会记录线程A的id,之后在没有别的线程获取锁对象的前提下,线程A在执行这个锁对应的同步代码块时不会再进行任何同步操作,即这个对象锁一直偏向线程A,这就是偏向锁。

比如更衣室中有很多衣柜,你在其中一个衣柜上写了你的名字,当下次要使用的时候发现衣柜上仍然是你的名字,此时直接使用即可,这就省去了上锁和用钥匙开锁的过程。

偏向锁的工作过程:

锁对象第一次被线程A获取的时候,jvm利用cas操作将线程id写入到锁对象的mark word中,此时锁会偏向线程A。

当有线程B来争抢锁的时候,会先查看拥有偏向锁的线程A是否存活,如果A已经结束或者不在同步代码块中,会将锁对象的标记改为无锁状态,然后升级为轻量级锁。

如果A仍然存活且在同步代码块中,偏向锁会升级为轻量级锁,A仍然会持有锁。

偏向锁的撤销是会耗费资源的,在jdk15中将其废弃(https://openjdk.java.net/jeps/374)

轻量级锁

早期的jdk中synchronized底层会转换为内核态使用操作系统的互斥量来保证线程的安全,这种方式导致在没有多线程竞争的时候效率低下,轻量级锁的出现就是解决了这个问题。

经过前辈们的研究发现大部分锁在整个同步周期内不存在锁竞争,在没有程竞争的时候会使用轻量级锁而不是转为内核态,这样就可以减少开销了。

轻量级锁的工作过程:

当同步代码即将执行的时候,倘若对象锁没有被某个线程拥有,jvm会在当前线程的栈中开辟一块叫做Lock Record的空间,来存储这个锁对象的mark word,之后jvm通过cas操作将锁对象的mark word替换为Lock Record的指针。

如果操作成功,则当前线程拥有这个锁对象。

如果失败,且不存在锁重入时,说明产生了竞争,此时轻量级锁会失效,膨胀为重量级锁,Mark Word中存储重量级锁的指针。

重量级锁

轻量级锁膨胀之后变为重量级锁,这里的重量级锁是相对轻量级锁而言的,重量级锁底层使用操作系统中的互斥量(mutex)。互斥量是可以处于解锁或加锁状态的变量,通常使用整数0表示解锁,其他值表示加锁。当线程要进入同步代码块时,如果互斥量是解锁的,则线程可以进入执行同步代码块。如果互斥量是加锁的,那么线程会阻塞,当有多个线程被阻塞时,系统会随机的挑选一个线程获取锁。

自旋锁

比如有两个线程A和B,其中A获取到了锁,此时,B不会挂起,而是时不时的去看看锁是否被释放了,即让B执行一个忙循环,这就是自旋锁,从代码的角度来看,自旋就相当于是循环。

线程的挂起和恢复都需要从用户态到内核态的转换,这显然会有额外的开销,而自旋锁的作用就是来解决这个额外的开销,不过自旋锁也是有缺点的,试想下,有很多线程都在自旋,这样会额外耗费CPU资源。自旋锁的适用场景是锁被占用的时间很短,且线程数量不是太多的时候。

比如只有一个厕所,张三和李四要去小便,张三先进去,(小便很快就结束,锁被占用的时间很短)此时李四会在门口等着。当两人去大便的时候,(大便时间比较长,锁被占用的时间长)李四通常就不会傻傻的在门口等了。

java引入了自适应自旋,jvm会根据以往的运行信息来判断一个锁是否可以轻松的通过自旋获取到,如果是,会允许其他获取该线程的锁自旋更多的次数,反之,jvm会直接挂起尝试获取这个锁的线程。

如下代码通过cas的方式实现了一个自旋锁,里面的do while就体现了自旋。

可重入锁

线程A在获取某个对象锁L之后,在执行对象锁L控制的其他同步代码块时,可以直接执行这段同步代码块,无需再次获取对象锁L,这就是可重入锁。例如下面代码中的obj,两个同步代码块都用到了obj锁,对于线程来说只需要第一次获取即可。

java中的synchronized和ReentrantLock都是可重入锁。

synchronized将重入的次数记录到了线程栈的Lock recored中。

ReentrantLock使用了内置变量记录重入的次数。

两者都使用了CAS保证了原子性,这样就可以根据重入的次数来正确释放锁了。

代码:

不可重入锁

线程A在获取某个对象锁L之后,在执行别的对象锁L控制的同步代码块时,不能获取到对象锁L导致阻塞的发生。下面示例中使用了wait,notify实现了一个不可重入锁。

测试类:

读写锁

读写锁,顾名思义有读和写,它允许多个线程同时读取共享数据,但是一次只允许一个线程对共享数据更新。读写锁通过读锁和写锁来完成读写操作,线程在读取数据前要获取读锁,读锁在读线程之间是共享的,可以被多个线程持有,写锁是排他的,有一个线程获取到写锁之后,别的线程无法获取读锁和写锁,这样保证了数据的一致性。

java中ReentrantReadWriteLock类就是读写锁。

独占锁

读写锁中的写锁就是独占的,即锁只能被一个线程持有,java中提供的synchronized和ReentrantLock都是独占锁。

共享锁

读写锁中的读锁是共享的,即锁可以被多个线程持有。

互斥锁

通常所说的互斥锁指的是mutex(互斥量),互斥顾名思义是相互排斥,即当一个线程持有互斥锁之后,其他线程就只能等待期释放才有机会去争抢。java中的synchronized和ReentrantLock都是互斥锁

乐观锁

系统乐观的认为数据被多个线程读取后不会被修改,因此不会上锁,当其中有线程修改这条数据的时候,系统会检查该数据是否有被修改过,如果否,则允许这次修改,否则修改失败。乐观锁是一种思想,适用于读多改少的场景。

我们可以利用版本号+CAS操作来实现乐观锁。

比如有一数据称之为A,我们为A添加一个版本号version初始值为0,A可以被多个线程访问,当有线程修改A的时候会先查看version是否为0,如果是,则允许这次修改且将0修改为1,这个操作是具有原子性的。倘若version不是0,则说明这期间有别的线程修改了数据A,这种情况由程序的设计者来决定如何进行后续的操作。

悲观锁

数据A不能被多个线程操作,当有一个线程操作数据的时候,会上锁,这样其他线程不允许操作,java中的synchronized,ReentrantLock等都是悲观锁。系统会悲观的认为数据总是会被修改。

公平锁

在公平锁中,线程会按照他们的请求顺序来获取锁,就像是买票排队一样,按照先来后到的顺序执行。ReentrantLock内部提供了公平锁的实现(Semaphore中也提供了公平锁的实现)。

ReentrantLock公平锁工作流程:

线程A获取到锁,线程B进入队列挂起,当A释放锁之后按照顺序要将锁交给B,此时新来的线程C也要争抢这把锁,C发现他前面有B,所以自己进入到队列中挂起,锁会被B获取。

非公平锁

相对于公平锁来说非公平锁允许线程插队,按照上面公平锁的工作流程来说,当新来的线程C要争抢这把锁的时候,会获取到这把锁,这样做的好处是减少了线程C的挂起和恢复操作,从而节约了开销,在大多数情况下,非公平锁的性能优于公平锁。ReentrantLock默认是非公平锁

分段锁

分段锁并不是具体的一个锁,其目的是细化锁的粒度。比如要保证数组中数据的线程安全,我们可以对其上锁,但是这样会影响效率,线程A在操作数组的时候,其他线程是不允许操作的。想一下如果线程A修改数组中下标0~9对应的元素,线程B要修改下标10~15的元素,这两个线程同时操作也不会出现线程安全问题,那可以对数组采用两把锁来控制,一把锁控制下标0~9的元素,另一把锁控制下标10~15的元素,这就是分段锁。相比于单个锁来说可以提高性能。java中的ConcurrentHashMap就采用了分段锁。

锁膨胀

锁膨胀,也叫做锁升级,指的是轻量级锁到重量级锁的转变。

锁消除

在JIT(即时编译器)运行的时候,发现某段加了同步的代码及时去掉同步也不会出现线程安全问题,就不会执行这个同步加锁的操作。锁消除的主要判定源于逃逸分析,即堆中的数据如果不会被其他线程访问到,则会认为这些数据不会出现线程安全问题。

比如下面代码运行的时候会执行锁消除,我本地耗时334ms。StringBuffer的append方法是synchronized修饰的,这里的StringBuffer对象仅仅在getString方法中会被修改,因此没有必要加锁。

通过下面参数关闭锁消除之后再次执行上面代码,我本机耗时779ms:

-XX:-EliminateLocks 锁粗化

一些连续操作都反复获取同一个锁,比如在循环体中反复获取同一个锁,这样的操作也会有额外的开销,此时jvm会将同步的范围扩大从而避免反复获取同一个锁,这个就是锁粗化。

比如下面代码:

锁粗化之后类似这样:

分布式锁

对于在同一个jvm中的数据,我们可以利用java提供的锁解决线程安全的问题,但是当要保证不同的jvm中数据线程安全问题的话,就需要使用分布式锁了。比如有100张电影票,分别放在了3台jvm中,这里就需要使用分布式锁来防止超卖的现象。

下图中如果没有分布式锁来控制,就很可能出现了超卖的现象

使用分布式锁来控制不同虚拟机中的票数

最后,如果有收获请帮我点个赞,您的赞同是我最大的收获。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有