[Java Web]关于各种各样的锁

[Java Web]关于各种各样的锁,第1张

1.读锁(共享锁)VS 写锁(独占锁)

Shared Lock(S锁) eXclusive Lock(X锁)

我们目前使用的锁都是独占锁(只有一个线程持有锁)

独占锁共享锁
读 + 读互斥不互斥
读 + 写互斥互斥
写 + 写互斥互斥

当业务中,读的次数 >> 写的次数,共享锁 优于 独占锁

synchronized 锁是独占锁

2.重入锁(ReentrantLock) VS 不可重入锁

是否允许持有锁的线程成功请求到同一把锁

t1线程

lock1.lock(); t1 成功锁上了 lock1

...

lock1.lock(); 此时 lock1 处于已经锁上的状态 && 请求锁的还是 t1 线程

那么这次加锁能否成功?

允许成功:可重入锁

不允许成功:不可重入锁

实现角度:是否要记录当前是哪个线程用锁 锁定了

synchronized 锁是可重入锁

3.公平锁(fair)   VS   不公平锁

当一把锁处于 锁 状态时,队列中有很多请求锁失败的线程们 在等待,当处于某一时刻,锁打开了,刚好有一个后来的新线程来请求锁,这个线程优先获取到锁了,那么就说明这个锁的实现不公平。应该把锁交给最先等的线程,而不是刚好此时运气好的线程。

非公平锁实现简单(默认)

公平锁实现复杂

公平锁:严格按照请求锁的次序获取到锁

synchronized 锁是不公平锁

lock 中 通过传入fair = true/false 来控制是否是公平的

Lock lock = new ReentrantLock(true);    // fair = true:使用公平锁模式

Lock lock1 = new ReentrantLock(false);    // fair = false:使用不公平锁模式

Lock lock2 = new ReentrantLock();               // 默认情况下是不公平的
4.乐观锁  VS  悲观锁

翻译的问题:这都不是锁。严格来讲,这两个是实现并发控制的两种不同的方案,和“锁”的概念不是一个层级的概念

乐观锁:评估后,并发情况,多个线程同时修改一个共享资源的情况比较少见,可以采用轻量级(无锁lock-free)方式,进行并发控制

悲观锁:多个线程会频繁地修改同一个共享资源,必须使用互斥的方式(锁lock)来进行并发控制

举个例子:

5.锁的实现方案

锁的实现导致的锁的种类

默认情况下,我们的锁的实现,是采用 OS 提供的锁(mutex 锁:互斥锁)

一旦请求锁失败,会导致当前线程(请求锁失败的线程)会放弃 CPU,进入阻塞状态,把自己加到锁的阻塞队列中,等待被唤醒。

必须进入到内核态,一旦放弃 CPU ,再到获取 CPU,时间相隔很久(站在 CPU 指令角度)

这种默认的互斥锁的成本较大。

需要一种不触发线程调度的锁的实现

硬件提供了 CAS 机制 ->  OS提供了 CAS 机制 -> JVM 提供了 CAS 机制

6.互斥锁(mutex)VS 自旋锁(spin lock)

 lock:

        boolean success = CAS(0x104,0,1);

        if(success){return;}

       // 加锁失败

互斥锁的方案:放弃 CPU... 引发线程调度。计算机只有一个核时的解决思路:早让持有锁的线程释放锁。

自旋锁的方案:

for(int i = 0;i < 1000;i++){ }  // 强行执行一些没有任何用途的指令,不放弃 CPU ,占着CPU ,等锁被释放

更适用于现代计算机:多核的模式 + 一般来说,锁的持有时间不会很长(线程放弃CPU到线程再持有CPU的时间是 远大于 锁的持有时长的),即使当前线程占用了一个核,也没关系。很快,另外的核上的线程就会释放锁。

7.synchronized 锁的实现与优化

策略:可重入的 + 不公平的 + 独占锁

实现:

(1)锁消除优化

        Vector v = new Vector();   v...

        前提:Vector 为了做到线程安全,每个方法都用 synchronized 修饰了

        但实际上,我们的代码中只有主线程 -> 所有做线程保护的 *** 作都是无用功(加锁、释放锁)

        编译器 + JVM 判断出只有一个线程时,就会消除掉所有锁的 *** 作,提升性能。

(2)锁粗化优化

        前提:已经没有办法进行锁消除的情况下

for(int i = 0 ;i < 1000;i++) {

        sync(lock) {

                number++;

        }

}

还原成指令集就是:加锁 -> n++ -> 解锁 -> 加锁 -> n++ -> 解锁  -> 加锁 -> n++ -> 解锁 ...

过于频繁的加解锁 *** 作,导致性能下降——锁的粒度太细了

优化:加锁 -> n++ ->  n++ ->  n++ ->  解锁 -> 加锁 -> n++ ->  n++ ->  n++ ->  解锁...

          比如每三次n++解一次锁,具体加几次解锁看实际情况。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/887040.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-14
下一篇 2022-05-14

发表评论

登录后才能评论

评论列表(0条)

保存