原子性问题的解决思路--锁

1,760 阅读2分钟

原子性问题的源头是线程切换,如果禁止线程切换,则不会产生原子性问题,但是线程的切换依赖于CPU的中断,多核环境下是无法实现的。列举一个典型的例子,在32位CPU上执行long行变脸的写操作,long型变量为64位,在32位CPU上执行写操作会分为两次,写高32位和写低32位。

多核场景下,同一时刻可能有两个线程同时执行,一个执行在CPU-1上,一个执行在CPU-2上,此时若禁止CPU中断,只能保证CPU上的线程连续执行,却无法保证同一时刻只有一个线程执行,如果两个线程同时写long型变量的高32位,则结果就会超出预期。

这里就有一个非常重要的条件:同一时刻只有一个线程执行,称之为“互斥”。如果可以保证对共享变量的操作是互斥的,那么原子性问题就可以得到解决。

锁的简易模型

锁的简易模型
把一段需要互斥执行的代码成为临界区,线程进入临界区之前,需要先加锁,如果加锁成功,则进入临界区,该线程持有锁;如果加锁失败,则线程等待,直到持有锁的线程执行完临界区的代码,释放锁。

锁的改进模型

在多线程的世界里,锁不是孤立存在的,通常情况下锁是用来保护某个资源的

锁的改进模型

synchronized关键字

java提供synchronized关键字来实现锁,他可以修饰方法块也可以修饰方法:

class X {
    //修饰非静态方法
    synchronized void foo() {
        //临界区
    }
    
    //修饰静态方法
    synchronized static void bar() {
        //临界区
    }
    
    //修饰代码块
    Object obj = new Object();
    void baz() {
        synchronized(boj) {
            //临界区
        }
    }
}

synchronized关键字隐式包含了lock()和unlock()操作,无需手动unlock。

关于加锁的对象,对于静态方法,锁定的是当前类的Class对象;对于非静态方法,锁定的就是当前的对象实例。