多线程知识梳理(2),当我们谈到synchronized关键字的时候,我们在谈什么?

一个对象是否是线程安全的,取决于它是否被多个线程访问。要是的对象是线程安全的,需要采用同步机制来协同对对象可变状态的访问。当一个线程访问某个状态变量并且其中有一个或多个线程执行写入操作时,必须采用同步机制来协同这些线程对变量的访问。Java中的主要同步机制是关键字synchronized


如果当多个线程访问同一个可变的状态变量时,没有使用合适的同步机制,那么程序就会出错误。有三种方式可以修复这个问题

  1. 不再线程之间共享改状态变量
  2. 将状态变量修改为不可变的
  3. 在访问状态变量时使用同步

当然刚才说的三个办法,其实前两个方法都是通过解决不了需求,就去解决提出需求的人的方式去搞定的线程不安全的问题。事实上,我们大部分的工作中都是希望我们通过第三种方案来解决线程不安全的问题。

这里我想额外插一句话,也是我觉得是在学习多线程过程中非常重要的一种编程方式:

首先使代码正确运行,然后再提高代码的速度。

也就是说如果你的代码逻辑运算结果和希望的有误差,你再多的使用多线程去提高速度也是没有意义的。就像是我们上学的时候去考试,考120分钟,结果你十分钟就交卷了,但是卷子上都是错误答案一样,这样做是没有意义的,也是不得分的。

好了,我们言归正传,说说我们本文中的主角synchronized。

synchronized是Java语言提供的一种内置的锁机制。每个Java对象可以用作一个实现同步的锁,线程再进入同步代码块之前会获得锁,并且在推出同步代码的时候释放锁。Java的内置锁相当于一种互斥体,它意味着最多有一个线程能够持有这种锁。而synchronized可以用来保证synchronized代码块中的代码的原子性、对共享变量修改后的可见性、代码块执行的顺序性。

如何保证原子性

synchronized加锁的原理,说白了就是在进入加锁代码块的时候,加一个monitorenter的指令,然后针对锁对象关联的monitor累加计数器,同时表示自己这个线程占有了这个锁。

举个例子:

MyObject lock = new MyObject();

synchronized(lock){

​ // 业务代码

}

可能很多现成的都需要执行这一段代码,这里的逻辑其实是这这样的是这样的,其实所有的线程都是对lock对象进行尝试加锁。这个时候执行了monitorenter指令,尝试对其进行加锁。执行释放锁的时候,执行monitorexist指令,递减计数器。如果计数器的值为0,就标志当前线程不持有锁,释放锁了。


这里需要深入分析一下,synchronized的加锁底层原理。

每一个对象的实例中,都包含两个内容:

  1. 对象头
  2. 实例变量

其中对象头中包含MarkWord和ClassMetaDataAddress两个属性。ClassMetaDataAddress就是一个指针,指向了这个对象对应的类信息的地址。

而MarkWord中就有许多对象中有意思的数据,如:hashCode、monitor、GC数据等等。这里我们需要说的就是monitor,这个东西其实本质上也是一个指针,指向了objectMonitor对象,这个对象中包含以下几个重要的属性:entrylist、count、owner、waitset

其中entrylist是多个线程都希望获取锁的时候,都再这里等待,具体是通过CAS来修改count值的方式来确定谁获取到了锁。

count的默认值为0,所以成功将count修改成为1的线程就是获取到所得线程。由于CAS,这个时候其他线程尝试修改count都会失败,然后触发重试。

加锁成功后的线程,会将owner的值指向自己。

如果获取到锁的线程执行wait指令,会释放锁,然后将自己放入objectMonitor对象的waitset中去,直到其他线程获取到锁,然后执行notifyall,将waitset中的所有线程释放,然后这些线程会重新加入entrylist尝试获取锁。

简单梳理一下流程:

  1. 从lock对象的对象头信息中,查看monitor指针指向的objectmonitor对象。
  2. 加入objectmonitor对象的entrylist列表
  3. 尝试修改count计数器,默认是0,这里是CAS机制,如果原始值不是0会继续重试,换句话说,其实哪个线程成功修改了count计数器,就可以说开始持有锁了
  4. 将owner指针指向自己线程
  5. 开始执行业务代码
  6. 如果还需要继续加锁(重入锁),count++
  7. 如果释放锁,count--
  8. 如果count == 0了,就算是完全释放锁了。
  9. 有一个特殊情况,如果线程执行了wait的话,那么它会释放锁,然后将自己放入到waitset中,从而不参与线程的竞争机制了。直到某个线程执行了notifyAll将waitset中的所有线程释放出来,这个时候这些线程会再进入entrylist里面尝试竞争获取锁

=================================未完待续=

猜你喜欢

转载自www.cnblogs.com/joimages/p/12768712.html