并发编程之线程安全
并发编程之线程安全
JAVA中锁的概念
自旋锁: 当线程去争抢对象的锁时,该对象锁已经被占用,当前线程就会循环等待,不断的去判断锁是否能够被获取,如果不能被获取,直到成功获取锁才会退出循环;
乐观锁:在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改;
悲观锁:假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁;
独占锁(写):给资源加上写锁,线程可以修改资源,其他线程不能再加锁; (单写)
共享锁(读):给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁; (多读)
可重入锁:线程拿到一把锁之后,可以自由进入同一把锁所同步的其他代码。
不可重入锁:线程拿到一把锁之后,不可以自由进入同一把锁所同步的其他代码,还需要重新获得锁
公平锁:争抢锁的顺序,如果是按先来后到,则为公平。
非公平锁:争抢锁的顺序,不按先来后到,则为不公平。
几种重要的锁实现方式:synchronized、ReentrantLock、ReentrantReadWriteLock
synchronized
为什么使用synchronized
看以下实例:
当不使用synchronized关键字时,并发20个线程,同时每个线程分别调用demo.add方法100次,执行加法运算,期望结果为:2000,但是输出结果又出现“1979”的情况,如下:
当使用synchronized关键字时,同样并发20个线程,同时每个线程分别调用demo.add方法100次,执行加法运算,期望结果为:2000,结果都为:2000
public class Counter {// 假设i 是一个spring容器里面的对象,共享对象private int i = 0;private static Object lock = new Object();//不加synchronizedpublic void add() {i = i + 1;}//加synchronizedpublic void add() {synchronized(lock){i = i + 1;}}public static void main(String[] args) {for(int i = 0; i < 200; i++) {run();}//输出结果:/**200019792000*/for(int i = 0; i < 200; i++) {run1();}//输出结果:/**......20002000*/}//并发20个线程,同时调用demo.add方法执行加法运算public static void run() {Counter demo = new Counter();List<Thread> threads = new ArrayList<Thread>();for(int j =0;j<20;j++){Thread th = new Thread(()->{for(int i = 0; i < 100; i++) {demo.add();}});th.start();threads.add(th);}for(Thread t : threads) {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(demo.i);}//并发20个线程,同时调用demo.add1方法执行加法运算public static void run1() {Counter demo = new Counter();List<Thread> threads = new ArrayList<Thread>();for(int j =0;j<20;j++){Thread th = new Thread(()->{for(int i = 0; i < 100; i++) {demo.add1();}});th.start();threads.add(th);}for(Thread t : threads) {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(demo.i);}
}
synchronized使用
正确使用
- 用于实例方法,隐式的指定锁对象,就是调用实例方法的对象;
- 用于静态方法,隐式的指定锁对象,就是当前类对象class
- 用于代码块,需要显示指定锁对象,可以指定为调用实例方法的对象,或者自定义的(静态或成员)对象
实例
//1. 用于实例方法,隐式的指定锁对象,就是调用实例方法的对象;public synchronized void add() {i = i + 1;}
//2. 用于静态方法,隐式的指定锁对象,就是当前类对象classprivate static int i = 0;public static synchronized void add() {i = i + 1;}
//3. 用于代码块,需要显示指定锁对象,可以指定为调用实例方法的对象,或者自定义的(静态或成员)对象
//--------------第一种:this
public void add() {synchronized(this) {i = i + 1;}
}
//--------------第二种:自定义对象private Object lock = new Object();public void add() {synchronized(lock) {i = i + 1;}}
//--------------第三种:静态自定义对象private static Object lock = new Object();public void add() {synchronized(lock) {i = i + 1;}}
synchronized特性
可重入、独占、悲观锁
特殊优化 :
锁消除(开启锁消除的参数:-XX:+DoEscapeAnalysis -XX:+EliminateLocks)
锁粗化 JDK做了锁粗化的优化, 但我们自己可从代码层面优化
synchronized关键字,不仅可以实现同步,JMM中,synchronized可以保证可见性(结果不能被缓存)
synchronized原理
JVM运行时数据区
JVM运行时数据区包含:线程栈、堆、方法区,如下如:
堆中Java对象
Java对象包含三部分:对象头、属性值、填充(字节对齐),如下图:
对象头Object Header
对象头包含:Mark Word 、Class Metadata Address、Array Length,如下图:
Mark Word
默认情况下JVM锁会经历:未锁定->偏向锁 -> 轻量级锁 -> 重量级锁 这四个状态
轻量级锁
在未锁定的状态下,可以通过CAS来抢锁,抢到的是轻量级锁
线程栈开辟一个空间,存储当前锁定对象的Mark Word信息。
Lock record可以存储多个锁定的对象信息。
使用CAS修改mark word完毕,加锁成功。则mark word中的tag进入 00 状态。
解锁的过程,则是一个逆向恢复mark word的过程
重量级锁
轻量级锁中的自旋有一定的次数限制,超过了次数限制,轻量级锁升级为重量级锁
Monitor对象可以参考openjdk中的objectMonitor中的信息
openjdk\hotspot\src\share\vm\runtime\objectMonitor.cpp
openjdk\hotspot\src\share\vm\runtime\objectMonitor.hpp
openjdk\hotspot\agent\src\share\classes\sun\jvm\hotspot\runtime\ObjectMonitor.java
偏向锁
在JDK6 以后,默认已经开启了偏向锁这个优化,通过JVM 参数 -XX:-UseBiasedLocking 来禁用偏向锁若偏向锁开启,只有一个线程抢锁,可获取到偏向锁。
偏向标记第一次有用,出现过争用后就没用了。 -XX:-UseBiasedLocking 禁用使用偏置锁定,
偏向锁,本质就是无锁,如果没有发生过任何多线程争抢锁的情况,JVM认为就是单线程,无需做同步
(jvm为了少干活:同步在JVM底层是有很多操作来实现的,如果是没有争用,就不需要去做同步操作)
锁的升级过程
锁升级的过程如下:
同步关键字加锁原理
HotSpot中,对象前面会有一个类指针和标题,储标识哈希码的标题字以及用于分代垃圾收集的年龄和标记位
默认情况下JVM锁会经历:偏向锁 -> 轻量级锁 -> 重量级锁这四个状态
参考来源:
https://www.cs.princeton.edu/picasso/mats/HotspotOverview.pdf
https://wiki.openjdk.java.net/display/HotSpot/Synchronization
wait/notify机制
wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。
notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。
注意:
只能在synchronized关键字中使用,且调用wait、notify的对象与锁对象相同,否则会抛IllegalMonitorStateException异常。
参考来源
-
https://www.cs.princeton.edu/picasso/mats/HotspotOverview.pdf
-
https://wiki.openjdk.java.net/display/HotSpot/Synchronization
ify的对象与锁对象相同**,否则会抛IllegalMonitorStateException异常。
[外链图片转存中…(img-0rNr3psI-1747909233174)]
[外链图片转存中…(img-Z7ADnGzk-1747909233174)]
参考来源
-
https://www.cs.princeton.edu/picasso/mats/HotspotOverview.pdf
-
https://wiki.openjdk.java.net/display/HotSpot/Synchronization
-
《深入理解Java虚拟机》