锁(synchronized)的到底是什么?

今天是2020.02.02,很有意义的一天,想着起码得写点东西纪念一下,哈哈哈。
言归正传。
最近在学并发,接触到synchronize这个关键字,接触到锁的概念,但是又经常迷糊这个锁,它到底锁住了什么?所以今天一探究竟。
在单线程的程序里我们可能不会考虑并发的问题,但是到了多线程里面我们就要考虑安全性问题、活跃性问题,性能问题等。
就拿最简单的例子来说:

public class UnsafeSequence {
    private int count;

    public int addOne() {
        return count++;
    }
}

对于count++的操作,我们通常认为它就是一条执行语句,其实不然,它是由多条语句构成的,这条语句起码做了以下三个动作:

  1. 获取count的值。
  2. 在count的基础上加1。
  3. 将计算以后的值重新赋值给count。
    所以这条语句是一个复合语句,在多线程下就会出现这样的情况:
图一

两个线程同时调用addOne()方法,在线程A没有将得到的值赋值给count之前,线程B获取的count值就也是1,原本两个线程调用完之后应该是3,结果count却是2,这其实是一种竞态条件(由于不恰当的执行时序而出现的不正确的结果),本质是基于一种可能失效的观察结果而做出的判断或者计算等操作,那我们得出的结果就不一定是正确的。而出现数据竞争的代码区域就称作“临界区”。
这就引出了接下来要说的synchronized,即对临界区加锁:

public class UnsafeSequence {
    private int count;

    public synchronized int addOne() {
        return count++;
    }
}

这个锁的作用是什么呢?
它把我们的复合操作变成了原子操作(原子操作可以理解为操作的最小的不可分割的单位,和事务很像),这样我们原来的count++操作就变成一个不可分割的操作,这样就不会出现上图的情况了。这里我们其实是将this这个对象锁了起来,这样其他操作想要调用addOne(),但是却获取不到这把锁,只能等待,当调用它的线程将锁释放掉,其他线程才有机会获取到这把锁。下面这个代码和上面我们说的这种加锁作用十分相似。

public class UnsafeSequence {
    private int count;

    public int addOne() {
        synchronized(this){
            return count++;
        }
    }
}

什么是this锁:


图二.png

可能不太准确,只是为了便于理解。对于this锁,假设实例化两个UnsafeSequence对象A、B,A和B分别调用addOne方法是不会相互影响,而不同线程都调用A.addOne()这就会引起互斥,也就是只有一个线程能够调用。

除了this锁,我们还有什么锁呢?

  1. “.class”锁
public class UnsafeSequence {
    private static int count;

    public synchronized static int addOne() {
        return count++;
    }
}

对于这种情况我们锁的是UnsafeSequence.class,对于这种情况该怎么理解呢?其实它就是针对了我们可能创建多个UnsafeSequence对象,但是这些对象的addOne操作是互斥的,也就是说多个对象的addOne方法在某一确定时间只能有一个对象且只有一个线程在执行该方法。(注:这里因为是static方法,所以它锁的是UnsafeSequence.class)


图三.png

对于上图,有三个对象同时抢一把锁,但是只有一个对象能够抢到,这张图没有体现同一对象在不同线程中争夺这把锁的情况(知道有这么一种情况就行)。

  1. “对象”锁
public class UnsafeSequence {
    private static int count;
    private Object object = null;
    
    public UnsafeSequence(Object object){
        this.object = object;
    }
    public int addOne() {
        synchronized (object){
            return count++;
        }
    }
}

这里我们声明了一个Object对象,然后对它进行加锁,如果想要执行addOne(),就必须获得锁住了object的这把锁(注意:这里要确保调用的addOne方法锁住的是同一个Object对象)。个人感觉这种方法十分巧妙,因为他能根据你的需求去锁定一些资源。


图四.png

对于上图,对象A、B传入的是object1,所以它们两个会互斥,而不会与对象C发生互斥。

你可能感兴趣的:(锁(synchronized)的到底是什么?)