浅析java锁机制及并发安全

  • 作者: 凯哥Java(公众号:凯哥Java)
  • 并发
  • 时间:2020-03-29 12:06
  • 2443人已阅读
简介 什么是线程安全1、进程与线程2、内存模型JMM1)对象都存储在主内存中。2)每个线程都有自己的独立的工作内存,里面保存该线程需要用到的变量的副本(主内存中该变量的一份拷贝)。3、内存可见性volatile:保证内存可见性,当一个线程修改了变量的值以后,其他线成也能看到这个值。保证可见行、不保证原子性、有序性(禁止指令重排序)。使用场景:作为开关状态变量、双重检查的单例。4、线程安全问题1)线程安全

🔔🔔🔔好消息!好消息!🔔🔔🔔

有需要的朋友👉:联系凯哥 微信号 kaigejava2022

什么是线程安全

1、进程与线程

2、内存模型JMM

1)对象都存储在主内存中。

2)每个线程都有自己的独立的工作内存,里面保存该线程需要用到的变量的副本(主内存中该变量的一份拷贝)。

3、内存可见性

volatile:保证内存可见性,当一个线程修改了变量的值以后,其他线成也能看到这个值。

保证可见行、不保证原子性、有序性(禁止指令重排序)。

使用场景:作为开关状态变量、双重检查的单例。

4、线程安全问题

1)线程安全

当多个线程访问一个类时,若无需考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及调用代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。线程安全的类封装了必要的同步,客户不需要自己提供。

2)线程安全问题产生原因

多个线程在操作共享数据;操作共享数据的线程代码有多条;

当一个线程在操作共享数据的多条代码时,其他线程进行了共享数据的写操作,导致线程安全问题产生。

02

synchronized锁分析

1、特点

1)基本特点

具有可重入性、非公平性、不可中断性、主动释放、只能绑定一个条件。

可使用于:代码块、方法、类。

2)底层机制

synchronized语句:当源代码被编译成字节码时,会在同步块的入口位置及出口位置分别插入mointorenter和monitorexit字节码指令。

synchronized方法:在class文件的方法表中将该方法的access_flags字段synchronized标志置1。

Jdk1.6之前,mointorenter及monitotexit依赖于操作系统的Mutex Lock来实现,而Mutex Lock需要将线程挂起并从用户态切换到内核台,这个切换过程非常消耗资源。Jdk1.6之后,对synchronized锁进行了大量优化,采用无锁状态、偏向锁、轻量级锁、Mutex Lock锁实现。

2、锁升级

1)Mark word状态变化

2)synchronized几种锁的比较

3、synchronized使用方式

1)Synchonzied同步代码块

2)Synchronized方法

4、synchronized案例1

1)用AddRunnable新建对象run1, run2;

再用run1新建thread1,run2新建thread2;

分别启动两个线程,最后输出x,x的结果是多少?

2)用AddRunnable新建对象run1, run2;

再用run1新建thread1,run2新建thread2;

分别启动两个线程,最后输出x,x的结果是多少?

3)用AddRunnable新建对象run1, run2;

再用run1新建thread1,run2新建thread2;

分别启动两个线程,最后输出x,x的结果是多少?

4)用AddRunnable新建对象run1, run2;

再用run1新建thread1,run2新建thread2;

分别启动两个线程,最后输出x,x的结果是多少?

5、synchronized案例2

1)双重检测的单例模式

未重排序

memory = allocate(); // 1 分配对象的内存空间

ctorInstance(memory); //2 初始化对象

instance = memory; //3 设置instance执行刚分配的内存地址

重排序

memory = allocate(); // 1 分配对象的内存空间

instance = memory; //3 设置instance执行刚分配的内存地址

ctorInstance(memory); //2 初始化对象

6、synchronized案例3

线程需要的锁被相互占有,synchronized锁会一直等待获取锁需要的锁,将会发生死锁。

1)死锁案例1: 模拟两个用户相互转账的场景

2)死锁案例2

根据对象在物理内存的地址值的大小顺序,进行加锁。

若Account具有唯一不变的userId,可根据userId值的大小,进行加锁,可省去加时赛。

03

AQS介绍

1、AQS简介

AQS即AbstractSynchonizedQueue队列同步器时用来构建锁或构建起他同步组建的基础,用一个int成员变量表示同步状态,通过内置的CLH队列(带头节点的双向非循环队列)来完成资源获取线程的排队,屏蔽了同步状态的管理、线程等待唤醒等操作。简化了锁的实现,方便我们通过继承来实现自定义的锁。AQS定义两种资源共享方式:Exclusive和Share。

2、AQS使用方式

同步器的使用方式

同步器的主要使用方式是继承,子类通过静态内部类来继承同步器并实现它的抽象方法来管理同步状态。子类中免不了对同步状态进行更改,提供了3个方法(getState()、setState(int newState)、compareAndSetState(int expect, int update))来进行操作,这几个方法能保证状态的改变是安全的。同步器定义了同步状态的获取和释放,由子类具体实现同步状态获取和释放操作。

3、独占式获取同步资源流程

4、共享式获取同步资源流程

04

ReentrantLock

1、特点

1)基本特点

支持锁重入、可中断、锁等待超时、提供公平/非公平锁、锁需要主动释放等;

2)底层机制

底层依赖AQS管理队列排队、阻塞、唤醒等,由内部类继承AQS。具体资源获取,由ReentrantLock实现。

非公平锁:若同时还有另一个线程来尝试获取,有可能会让这个线程抢先获取,默认非公平。

公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己的前驱节点不是在队首的话,就会排到队尾,由前去节点为头节点的线程获取到锁。

2、主要方法

3、避免锁顺序死锁

4、与synchronized锁比较

05

读写锁

1、基本特点

1)使用场景

ReentrankLock保护了数据一致性,但是很强的加锁约束,会限制并发性。互斥避免了“写/写”及“读/写”的重叠,同样避开了读读“的重叠。有些时候,多数访问只进行读操作,如能保证读取时没有其他线程修改数据,就不会发生问题。

ReentrankLockReadWriteLock适用以上场景:读多写少。保证多个线程可同时读访问,或者被一个线程写访问,两个线程不能同时进行读写操作。

2)原理

ReentrankLockReadWriteLock提供两种锁:ReadLock(读锁)、WriteLock(写锁)。 ReadLock共享式资源,内部静态类sync继承AQS,提供共享式锁的获取与释放。WriteLock独占式资源,内部静态类sync继承AQS,提供独占式锁的获取与释放功能。

ReentrankLockReadWriteLock提供公平与非公平功能,默认非公平。

2、读写锁用在缓存场景

3、锁降级

线程A先获取读锁,若发现cache不可用,则先释放读锁在获取写锁,对数据更新。在获取读锁,释放写锁进行锁降级。在使用数据。

若不使用锁降级,直接释放写锁,线程B对data进行了更改,而线程A无法感知到,出现了数据错误。

若线程A执行了锁降级,线程B则无法获取到写锁,一直阻塞,直到线程A释放了读锁。


https://www.sohu.com/a/281648246_771850

TopTop