java并发编程学习笔记(5):锁

进程与线程上下文切换为什么消耗资源?

jvm是运行在操作系统(OS)上的,jvm没有权限直接操作底层的硬件。

jvm(用户态)通过借助OS(内核态),让OS通知CPU进行上下文切换。
在这里插入图片描述

原因:

  1. 需要从用户态切换到内核态
  2. 同时还要记录上下文的位置

并行与并发、线程安全与同步的区别

  • 多线程: 指的是这个程序(一个进程)运行时产生了不止一个线程

  • 并行: 多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。

  • 并发: 通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

  • 线程安全: 经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果

  • 同步: Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。

JAVA的锁体系

在这里插入图片描述

乐观锁和悲观锁

在这里插入图片描述

是否认为有别的线程修改数据是否加锁适用场景
悲观锁写操作多(加锁保证数据正确)
乐观锁读操作多(性能提升)

自旋锁和非自旋锁(互斥锁)

1.为什么要让线程自旋

阻塞或唤醒一个线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。所以,可以让请求锁的线程自旋(“稍等一下”),看看持有锁的线程是否很快就会释放锁。

自旋锁优点:自旋等待避免了线程切换的开销。

2.自旋锁与非自旋锁的对比

在这里插入图片描述

3.自旋锁的缺点

自旋锁不能代替阻塞,他要占用CPU时间片。如果锁被占用过久(此时自旋等待也会很久),造成浪费CPU资源。

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

jdk1.6以前,synchronized依赖于OS内核(即重量级锁),在jdk1.6之后进行了优化,引入这四个概念(即这四个专门针对synchronized锁的不同状态)。

在这里插入图片描述
锁状态的升级如上图。在达到重量级锁后,完成后,会释放锁,然后再来新一轮的锁的升级。

定义特点
无锁所有线程都能访问并修改同一个资源。但同时只有一个线程能修改成功修改资源的操作是一个循环。线程会不断尝试修改共享资源
偏向锁一段同步代码一直被一个线程访问,那么该线程会自动获取锁,降低获取锁的代价通过对比Mark Word解决加锁问题,避免执行CAS操作
轻量级锁当锁是偏向锁时,被另外的线程锁访问(此时,升级为轻量级锁)用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能
重量级锁等待锁的线程全部阻塞将除了拥有锁的线程以外的线程都阻塞

公平锁和非公平锁

定义优点
公平锁多个线程按照先来后到的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁等待锁的线程不会饿死(肯定会排到,只是时间问题)等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大
非公平锁多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待(欧皇能获得锁)减少唤起线程的开销,整体的吞吐效率高处于等待队列中的线程可能会饿死,或者等很久才会获得锁(非酋)

公平锁示意图:
在这里插入图片描述

非公平锁示意图: D可能插队成功,也可能插队失败。
在这里插入图片描述

可重入锁(递归锁)和不可重入锁

定义特点
可重入锁在同一个线程在外层方法获取锁的时候,再进入线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class)不会因为之前已经获取过还没释放而阻塞,一定程度避免死锁
不可重入锁与上相反

1.一个经典的例子说明可重入锁与不可重入锁的区别

public class Widget {
    public synchronized void function1() {
        System.out.println("方法1执行...");
        function2();
    }
 
    public synchronized void function2() {
        System.out.println("方法2执行...");
    }
}

方法1和方法2都是被synchronized(可重入锁)修饰的。方法1中调用方法2。由于是可重入锁。所以同一个线程在调用方法2时可以直接获得当前对象的锁,进入方法2进行操作。

若是不可重入锁:当前线程方法1在调用方法2时,需要将方法1的锁释放掉,实际上对象锁已被当前线程锁持有,无法释放,所以此时会出现死锁。

2.可重入锁和不可重入锁模型

(1)可重入锁模型
在这里插入图片描述
(2)不可重入锁模型
在这里插入图片描述

独享锁(排他锁)和共享锁

定义特点
独享锁该锁一次只能被一个线程持有获得排他锁的线程能读写数据
共享锁该锁可以被多个线程持有获得共享锁的线程能读,不能写数据

显示锁和隐式锁

隐式锁
synchronized是基于jvm的内置锁,加锁与解锁的过程不需要我们在代码当中人为控制,jvm会自动去加锁和解锁。
显示锁
ReentrantLock的加锁和解锁需要我们手动编写代码去控制。

synchronized

synchronized在jdk1.6以前
在1.6以后

synchronized是利用JVM实现的,是JVM的内置锁。通过内部对象Moniter(监视器锁)实现,基于进入与退出Moniter对象实现方法与代码快的同步,监视器锁的实现依赖底层OS的Mutex Lock(互斥锁)实现。
在这里插入图片描述

为什么ReentrantLock是一个悲观锁?

  1. 因为ReentrantLock的底层是通过定义一个Snyc来用AQS(抽象队列同步器)来实现的。(图1)

在这里插入图片描述

  1. 而AQS又继承了AbstractOwnableSynchronizer(图2)在这里插入图片描述

  2. AbstractOwnableSynchronizer中又用到一个独占锁(所以是悲观锁)。
    在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值