从分布式锁角度理解Java的synchronized关键字

分布式锁

分布式锁就以zookeeper为例,zookeeper是一个分布式系统的协调器,咱们将其理解为一个文件系统,能够在zookeeper服务器中建立或删除文件夹或文件.设D为一个数据系统,不具有事务能力,在并发状态下可能出现对单个数据同时读写.客户端A,B是数据系统D提供的客户端,可以对其读写.java

几个关键角色已经登场,D是一个不提供事务行为的数据系统,其存放的数据可被读写,在单客户端条件下能够保证数据的可靠,可是在两个客户端可能并发请求时就变得不可靠,A写的数据可能被B覆盖,B读的数据多是A没有写完的数据.在不修改D源码为其提供原子性操做的前提下,咱们能够考虑分布式锁.服务器

客户端的原始API并发

void write(){
    // 写数据
}
void read(){
    // 读数据
}

如何作呢,核心就是以zookeeper为媒介.分布式

修改客户端API,若是读或者写,咱们首先要在zookeeper上建立一个文件夹,若是建立成功,咱们就执行读写操做,若是不成功(表明已经有人建立了)咱们就一直尝试建立,直到建立成功.当建立成功时,对D系统的数据进行读写操做,完成后删除zookeeper上建立的文件夹并退出读写方法.this

void write(){
 while(在zookeeper上建立文件夹) {
     写操做;
     删除zookeeper的刚建立的文件夹;
     return;
 }   
}

这样就完成了一个分布式并发行为的同步.假设A已经建立了文件夹,B就没办法进行后续操做,会一直尝试建立文件夹,A执行完操做以后删除文件夹,B才有机会进行在D系统上的操做.这个例子中产生了一个临界资源:D系统的数据,和一个竞态条件:A和B并发读写.(实际上zookeeper中是能够为监听者发送文件状况的,好比我建立文件夹没有成功,能够监听该文件夹,当文件夹变化时会通知你,这时候你就能够再尝试建立文件夹了(很像wait和notify),可是为了避免把重心放在zookeeper上就没有改为监听模式)线程

Java中Synchronized关键字

那咱们再回到Java中的synchronized关键字上来,若是你还没理解上面的行为和synchronized的关系你能够继续日后看.code

synchronized究竟锁住的是什么呢,锁住的是一块内存,咱们在内存中的某个位置设置一个值为0,当该值为0时,一个线程为了修改临界资源将其设置为1,而后读写操做,读写结束将其设置为0,其余线程能够再次将其改成1,进行读写,结束后再将其置为0......这块内存存储在每一个对象的对象头中,咱们称其为锁标志位(实际上所标志位不是简单的0和1,锁标志位分为四种状态:无锁,偏向锁,轻量级锁,和重量级锁,并发中还涉及锁升级等,但本文不作细究,有兴趣的读者能够查阅相关资料).对象

当进入synchronized代码块或者方法的时候,你会像上面说的分布式锁那样"建立一个文件夹",其余线程没法"建立文件夹"就会一直去尝试,当代码块或方法结束的时候会"删除文件夹",其余线程能够去抢夺建立文件夹的机会.synchronized能够看作一道门,锁住的对象能够看作是门票.千万不要认为synchronized锁住的是临界资源,synchronized是以某个对象的锁标志做为入场券去约束竞态线程之间行为:我只有一张门票,我只给一我的.事务

看一下synchronized的用法,多个线程之间的竞态行为必定要是用相同的门票去约束.内存

// 这锁住的是
class Foo{
    // 这锁住的是类对象
    static synchronized void bar();
    // 这锁住的是this,即实例对象
    synchronized void bar();
    // 等价于static synchronized
    void bar(){
        synchronized(Foo.class){};
    }
    // 等价于synchronized实例方法
    void bar(){
        synchronized(this){}
    }
}

对于静态方法须要注意一点(假如你想观察偏向锁的变化),实例方法能够直接观察实例对象的对象头上的锁标志位,可是静态方法须要观察Foo.class对象的锁标志位,若是观察实例对象的锁标志位会竹篮打水哟.

相关文章
相关标签/搜索