由synchronized闲谈锁机制

最近正在按照大神指点,一点点去复习知识,正好看到了Synchronized这块,本着多看一层可以装逼的思想,就有了这篇文章。
Synchronized可以说是我们经常用到的,也就是我们经常说的锁机制,我们为了防止多个线程访问保证单一和安全性,就采用了锁机制。Synchronized经常在以下几种情况下使用
- 方法使用
public synchronized void printLog(String content) {
for (int x = 0; x < 5; x++) {
Log.e(TAG, "print: " + content + "---" + x);
}
}
这种方法下,我们锁住的其实是类的实例化对象,只和调用方法的对象有关。
- 静态方法使用
public static void printLog(String content) {
for (int x = 0; x < 5; x++) {
Log.e(TAG, "print: " + content + "---" + x);
}
}
在这种情况下锁住的其实是类对象,不管多少个线程,多少个类的实例化对象,都得老实的排队进入。
- synchronized (this)
public void printLog(String content) {
synchronized (this) {
for (int x = 0; x < 5; x++) {
Log.e(TAG, "print: " + content + "---" + x);
}
}
}
这种情况下锁住的其实还是类对象,线程再多,对象再多,也得老实等着别人释放锁。
- synchronized (xxx.class)
public void printLog(String content) {
synchronized (DemoClass.class) {
for (int x = 0; x < 5; x++) {
Log.e(TAG, "print: " + content + "---" + x);
}
}
}
这种情况和上面一样,再多对象和线程也得老实等着别人释放锁。
- synchronized (变量)
private static final String TAG = "DemoClass";
public void print4(String content) {
synchronized (TAG) {
for (int x = 0; x < 5; x++) {
Log.e(TAG, "print: " + content + "---" + x);
}
}
}
这种情况和上面一样,都是需要老实等待别人放锁才能进去。

通过上面我们基本可以总结出来什么呢?
作为锁的资源只要是独一份那种,那么作为对象来说,线程再多也是安全的。像上面加锁方法,是因为实例化以后,每个实例化对象自己内部会保存一份这个方法,锁并不是相同的一个。
静态资源和类文件,包括某个对象,从某种角度来说都是独一份的存在。 如果锁的是类对象的话,尽管new多个实例对象,但他们仍然是属于同一个类依然会被锁住,即线程之间保证同步关系。

其实我个人对锁这个词语一直不太满意,我比较喜欢用钥匙来理解。毕竟加锁的方法就好比带锁的房间,有钥匙才能进入,什么是钥匙呢?其实就是所谓锁的存在。
synchronized是怎么实现线程安全的?
这里其实牵扯到一个东西,叫对象的监视器即Monitor。每个对象自身都会带着一个Monitor,相当于一个钥匙。

synchronized代码块我们可以理解为一个带锁的屋子,每次只能进去一个人,作为锁的对象理解为把门人。当进入的时候,先到的线程就会找把门人要钥匙,说我要进去玩,把门人这时候有钥匙就把钥匙给了它,它就能进去。如果此刻来了其他线程,和把门人要钥匙,把门人摊手说,钥匙还么给我呢,等上个人还了钥匙再进去吧。这里的钥匙就是作为锁对象的Monitor。
syncrhoized又叫做内置锁,为什么呢?因为使用syncrhoized加锁的同步代码块在字节码引擎中执行时,其实是通过锁对象的monitor的取用与释放来实现的。由上面我们说道Monitor是内置于任何一个对象中的,syncrhoized利用monitor来实现加锁解锁,故syncrhoized又叫做内置锁。 所以syncrhoized(lock)来加锁时,锁对象可以是任意对象了。
详情大家可以参考这篇文章
https://www.cnblogs.com/ygj0930/p/6561667.html
这里可以简单讲一下重入锁概念,什么是重入锁呢?也就说当一个线程当前已经申请到一个锁的时候,如果允许的代码里还有需要申请到该锁的情况,那么会直接拿到该锁,不需要进行排队等待。这里会涉及一个计数器概念,每次获取锁,计数器都会+1,释放锁,计数器-1,直到计数器为0为止,才能保证锁被彻底释放,别的线程才能够进行锁的申请。
详情可以参考这篇文章 https://www.cnblogs.com/Andya/p/7272276.html
下面讲一下悲观锁概念

synchronized其实就是典型的悲观锁,在这种思想认为每次数据的操作都是不安全的,都有可能触发线程并发的问题,所以每次都是保证自己独占本次数据操作,将数据锁定。它的事务要想更新,必须等以前的事务提交或者回滚解除锁,其实这种在大并发的时候是很降低效率的。

为了提高数据的操作效率又引入乐观锁(无锁)概念。认为系统中的事务并发更新不会很频繁,即使冲突了也没事,大不了重新再来一次。它的基本思想就是每次提交一个事务更新时,我们想看看要修改的东西从上次读取以后有没有被其它事务修改过,如果修改过,那么更新就会失败。
乐观锁引入了CAS技术,什么是CAS技术呢?
CAS的全称是Compare And Swap 即比较交换,其算法核心思想如下
执行函数:CAS(V,E,N)
其包含3个参数
- V表示要更新的变量
- E表示预期值
-
N表示新值
通俗的理解就是CAS操作需要我们提供一个期望值,当期望值与当前线程的变量值相同时,说明还没线程修改该值,当前线程可以进行修改,也就是执行CAS操作,但如果期望值与当前线程不符,则说明该值已被其他线程修改,此时不执行更新操作,但可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作。
ABA咋办
CAS技术可能遇见ABA问题
比如线程1准备用CAS修改变量值A,在此之前,其它线程将变量的值由A替换为B,又由B替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了。为了解决ABA最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号就可以解决ABA问题。
其实CAS还会牵扯到自旋和锁膨胀问题
其实自旋就就是当一个线程 需要同步数据的时候,发现另外一个线程正在占用资源锁就自己循环等待别人把锁释放出来。
这里涉及轻量级加锁和解锁问题
轻量级锁加锁:线程在执行同步块之前,JVM 会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的 Mark Word 复制到锁记录中,官方称为 Displaced Mark Word。然后线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
轻量级锁解锁:轻量级解锁时,会使用原子的 CAS 操作来将 Displaced Mark Word 替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。
锁膨胀成重量级锁就其实就是为了避免线程争夺损耗,就好比俩人都拉着厕所门不让对方进去,这时候其中一个直接进去锁死门,另外一个只能眼睁睁等着他完事自己才能进去。
其实锁机制牵扯东西很多,我也就是这两天看到的一些东西,以后如果有新学习到的东西也会继续补充,就这样吧