synchronized这个关键字相信大家都不陌生,在多线程并发的情况下使用它来保障共享资源的安全应该是最简单的方式。但是为了高效不应该因为其使用简单而放肆的滥用它,我们应该知其然而知其所以然,这样才能更好的高效开发并且提升自己的知识深度。
那我废话不多说了,咱们开始吧!
synchronized是Java为多线程并发情况提供的一种保障共享资源安全的一个关键字,它需要作用于对象上。
原子性是指一个操作或者一组操作,要么全部执行且执行过程不会被任何因素打断,要么就都不执行。
synchronized 对临界资源加锁时,保证了临界资源对于线程间的原子性。比如高清无码资源被线程A拿到,并且因为资源非常好所以线程 A 多次进入资源进行使用(可重入),但是此时线程 B 也想来观摩观摩,但不好意思资源已被 A 占用,即使 A 退出资源一次但还没有全部退出时,线程 B 只能甘等。
有序性值程序执行的顺序按照代码先后执行
Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。
可见性是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的
synchronized对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新到主存当中,保证资源变量的可见性,如果某个线程占用了该锁,其他线程就必须在锁池中等待锁的释放。
当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。
synchronized 一共有三种使用方法并且锁的对象各不相同:
在这种情况下多线程并发访问实例方法时,如果其他线程调用同一个对象的被 synchronized 修饰的方法,就会被阻塞。
相当于把锁
记录在这个方法对应的实例对象
上。
public synchronized void set(){
i++;
}
复制代码
在这种情况下进行多线程并发访问时,如果其他线程也是调用属于同一类的被 synchronized 修饰的静态方法,就会被阻塞。
相当于把锁
信息记录在这个方法对应的类
上。
public synchronized static void set(){
i++;
}
复制代码
如果此时有别的线程也想访问某个被 synchronized(对象A) 修饰的同步代码块时,也会被阻塞。
同步代码块会把锁
记录在对象A
上
public void set(){
synchronized(对象A){
i++
}
}
复制代码
synchronized的三种锁示意图:
在理解锁实现原理之前先了解一下Java的对象头和Monitor
首先我们来看看对象的内存布局
内存中的对象一般由三部分组成,分别是对象头、对象实际数据和对齐填充。大家可以看看我的这篇博客详细了解《链接》
对象头包含 Mark Word(对象头)、Class Pointer(类型指针)和 Length(数组长度) 三部分。
我们刚才讲的锁 synchronized 锁使用的就是对象头的 Mark Word 字段中的一部分,Mark Word 中的某些字段发生变化,就可以代表锁不同的状态。
由于锁的信息是记录在对象里的,有的开发者也往往会说锁住对象这种表述。
在32位和64位操作系统上 Mark Word 的结构图