一.锁所要解决的问题
原子性(一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行)
多线程中,存在线程调度和切换,原子性就会被破环。导致运行结果出现问题
二.锁(Synchronized)——保障原子性
1. 本质:
共享资源
2. 使用
synchronized有三种方式来加锁,不同的修饰类型,代表锁的控制粒度:
a. 实例锁/对象锁:修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁.
如下t1,t2 互斥,和t3不互斥
public class SynchronizedDemo {
private Integer i = 1;
synchronized void demo(){
System.out.println(i);
try {
Thread.sleep(10000);
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo=new SynchronizedDemo();
SynchronizedDemo synchronizedDemo2=new SynchronizedDemo();
//立即输出。
new Thread(()->{
synchronizedDemo.demo();
},"t1").start();
//10s后输出
new Thread(()->{
synchronizedDemo.demo();
},"t2").start();
//立即输出
new Thread(()->{
synchronizedDemo2.demo();
},"t3").start();
}
}
b. 修饰静态方法或类,作用于类对象加锁,进入同步代码前要获得当前类对象的锁
如下两种 t1 和 t2 互斥
public class SynchronizedDemo {
private static Integer i = 1;
synchronized static void demo(){
System.out.println(i);
try {
Thread.sleep(10000);
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo=new SynchronizedDemo();
SynchronizedDemo synchronizedDemo2=new SynchronizedDemo();
//立即输出。
new Thread(()->{
synchronizedDemo.demo();
},"t1").start();
//10后输出
new Thread(()->{
synchronizedDemo2.demo();
},"t3").start();
}
}
public class SynchronizedDemo {
private Integer i = 1;
void demo(){
synchronized(SynchronizedDemo.class){
System.out.println(i);
try {
Thread.sleep(10000);
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo=new SynchronizedDemo();
SynchronizedDemo synchronizedDemo2=new SynchronizedDemo();
//立即输出。
new Thread(()->{
synchronizedDemo.demo();
},"t1").start();
//10后输出
new Thread(()->{
synchronizedDemo2.demo();
},"t3").start();
}
}
c. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁
package org.example;
/**
* 风骚的Mic 老师
* create-date: 2020/5/17-20:17
*/
public class SynchronizedDemo {
private Integer i = 1;
void demo(){
synchronized(i){
System.out.println(i);
try {
Thread.sleep(10000);
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo=new SynchronizedDemo();
SynchronizedDemo synchronizedDemo2=new SynchronizedDemo();
//锁的互斥性。
new Thread(()->{
synchronizedDemo.demo();
},"t1").start();
//10s后输出
new Thread(()->{
synchronizedDemo2.demo();
},"t2").start();
}
}
三.锁的升级:
无锁:
当一个对象刚开始new出来时,该对象是无锁状态。此时偏向锁位为0,锁标志位01
偏向锁:
当有线程上锁指的就是把markword的线程ID改为自己线程ID的过程,就会成为偏向锁。(偏向锁的目的是消除数据在无竞争情况下的再次进入临界区的同步操作带来的消耗,进一步提高程序的运行性能。偏向锁的场景太过理想化,更多的时候会有其他线程进入临界区,抢占资源。JVM中默认不开启)
#开启偏向锁
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 -client -Xmx1024m -Xms1024m)
#关闭偏向锁
-XX:+UseBiasedLocking -client -Xmx512m -Xms512m
轻量级锁:
如果偏向锁被关闭或者当前偏向锁已经已经被其他线程获取,那么这个时候如果有线程去抢占同步锁时,锁会升级到轻量级锁,线程在自己的线程栈生成LockRecord,用CAS操作将markword设置为指向自己这个线程的LockRecord的指针,设置成功者得到锁。未成功会进行自旋,多次请求。
重量级锁:
如果竞争比较剧烈,有线程超过10次自旋( -XX:PreBlockSpin),或者自旋线程数超过CPU核树的一半,(1.6之后,加入自适应自旋adapative self spinning,根据上一次得到锁的时长,JVM自己控制)升级为重量级锁, 向操作系统升级资源,等待操作系统的调度,然后再映射到用户空间;
多个线程竞争同一个锁的时候,虚拟机会阻塞加锁失败的线程,并且在目标锁被释放的时候,唤醒这些线程;Java 线程的阻塞以及唤醒,都是依靠操作系统来完成的:os pthread_mutex_lock() ;因此重量级锁会消耗大量性能。
四.锁的存储
锁的信息存储再对象头中,Synchronized(obj)通过对信息的解析,判断是否可以进行同步代码块
1. 对象头:
(1) 32位虚拟机普通对象头: Mark word (32bits)+ Klass Word (32 bits)
Mark word :主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode;
Klass Word:是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例;
32位虚拟机对象头 Mark word分布
64位虚拟机对象头 Mark word分布
(3)打印类的布局(以64位为例)
1)导入jar 包
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
2)未加锁测试
public class ClassLayoutDemo {
public static void main(String[] args) {
ClassLayoutDemo classLayoutDemo=new ClassLayoutDemo();
System.out.println(ClassLayout.parseInstance(classLayoutDemo).toPrintable());
}}
开启压缩指针后对象头总共96位,(可在JVM配置关闭指针压缩 -XX:-UseCompressedOops,关闭后为 Klass Word为 64 位,总共128位)
org.example.ClassLayoutDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) //mark word
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) //mark word
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315) //kclass word
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
makr word 按照大端排列。重新排列成二级制为 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 //最后三位001无锁状态
3)加锁测试
public class ClassLayoutDemo {
public static void main(String[] args) {
ClassLayoutDemo classLayoutDemo=new ClassLayoutDemo();
synchronized (classLayoutDemo){
System.out.println(ClassLayout.parseInstance(classLayoutDemo).toPrintable()); }}
}
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 18 f5 2b 03 (00011000 11110101 00101011 00000011) (53212440)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
makr word 重新排列成二级制为 00000000 00000000 00000000 00000000 00000011 00101011 11110101 00011000// 最后三位000 轻量级锁