14-synchronized保证线程安全的原理(理论层面)

我们本讲开始来正式解决我们之前所遇到的线程安全性问题,回顾之前的内容,我们实现了一个数值序列生成器,那么,这个数值序列生成器呢,在我们多线程的环境下,会发现,生成的数值序列不是我们所预期的那种递增的序列,而是会出现那种我们所不预期的错误,不符合我们预期的错误,但是,我们当时的解决方案比较简单,就在那个方法上加了一个synchronized,于是,这个问题就解决了,我们并没有去详细的解释为什么在方法上加了一个synchronized就能够解决,那么,从本节课开始,我们来正式的了解解决线程安全性问题的各种的方案,首先,第一个就是synchronized,我们后面还会学习其他的锁呀、包括我们的元子类等等。那么,首先我们来看synchronized它为什么就能够解决我们的线程安全性问题呢?在看synchronized之前,我们首先来看内置锁,什么是内置锁呢?就是说,Java中每一个对象都可以用作同步的锁,那么,这些锁就被称之为内置锁,什么意思呢?我们来看代码,

14-synchronized保证线程安全的原理(理论层面)

这是我们的序列生成器,我在这里加了一个synchronized,这个线程安全性问题就解决了,那么,每一个Java中的对象都是一个内置锁,那么,这个线程在进入同步代码块之前,都必须先获得锁,也就是获得内置锁,然后,获得锁之后,它就可以进入到同步代码块中执行了,那么,这个线程获得到锁之后,其他的线程就没法再获得了,只有等这个线程把同步代码块中的代码执行完毕,或者这个线程抛出异常等等,总之就是这个线程释放了锁之后,其他的线程才能够再进来,那么,也就保证了被synchronized修饰的这个方法的方法体中(同步代码块中)相当于一个原子型操作,这样也就保证了线程的安全性,那么,这里提到了几个问题,第一个就是说,锁是互斥的,什么叫做锁的互斥呢?比如还以这个方法作为例子,

14-synchronized保证线程安全的原理(理论层面)

多个线程一块来执行这个方法,

14-synchronized保证线程安全的原理(理论层面)

于是这四个线程同时抢占CPU资源,结果,第一个线程抢占到了CPU资源,抢占到之后,它就会进到这个方法中,进到这个方法,首先就会拿到这个synchronized所持有的对象锁,它所持有的对象锁是谁呢?也就是所谓的内置锁,就是当前的this对象。

synchronized放在方法上,也就是说实例方法,普通方法上,那么,内置锁,也就是所谓的对象锁,就是当前类的实例,

14-synchronized保证线程安全的原理(理论层面)

那么,也就是说,我们虽然没有显示的去指定内置锁,但是,它会默认的给我们指向当前类的实例,那么,这个类的实例就是当前的这个对象,每一个对象都可以用作同步锁。拿到这个对象之后,其他的线程再想进来,那么,就必须等待,于是,这个线程开始往下执行,我们之前也都翻译过了,value++是执行了好多的字节码指令,那么,等字节码指令执行完毕之后,这个线程就可以释放掉锁了,把它拿到的锁释放掉,

14-synchronized保证线程安全的原理(理论层面)

于是后面的三个线程接着再进行竞争CPU资源,竞争这个锁,谁拿到了这个锁之后,谁就开始进到同步代码块中来执行,以此类推,那么也就是说,在同一时刻,这一个方法体中,只有一个线程能进来执行,那么,也就保证了我们这个方法体所执行的原子性。

我们刚才提到了互斥,互斥是什么意思呢?就是,一个线程进来,另外的线程不能够进来。

好了,关于内置锁和互斥锁我们都说完了。

接着我们再往下说,我们说,这个synchronized它除了能用在方法上以外,它还能用到哪里呢?除了修饰普通方法,还能修饰静态方法,也就是类的方法,那么,我们再来一个例子。

14-synchronized保证线程安全的原理(理论层面)

那么,当synchronized修饰了静态方法的时候,我们说,它修饰普通方法的时候,内置锁就是当前类的实例,如果它修饰静态方法,那么,内置锁是什么呢?它还能是当前类的实例吗?显然是不能的,类的方法上怎么会有实例呢,肯定是不会的,那么,它的内置锁是什么呢?内置锁是当前的Class字节码对象,这个能理解吗?在本例中就是所谓的Sequence.class。

14-synchronized保证线程安全的原理(理论层面)

那么,除了上面我们所说的修饰静态方法和修饰普通方法以外,它还可以修饰代码块。在修饰方法的时候,默认这个对象锁已经给默认了,修饰普通方法时就是我们当前类的实例,修饰静态方法时就是Class字节码对象,那么,它除了修饰方法以外,它还能够修饰代码块。

举个例子,如果我们想使用同步代码块来修饰它,可以这样写

14-synchronized保证线程安全的原理(理论层面)

这里我们看到它有一个参数,那么,这个参数是什么呢?其实就是一个Object。所有的锁,所有的内置锁,都可以当作这个参数,本例中这样写

14-synchronized保证线程安全的原理(理论层面)

这里写this,就类似于我们写在xx()方法上的synchronized,写在xx()方法上的synchronized的内置锁就是this,那么,不写this可不可以呢?比如说我们就写value

14-synchronized保证线程安全的原理(理论层面)

这个value也是一个对象,所以,这里也可以写value吗?value是一个基本数据类型,我们可以把它转成对象类型

14-synchronized保证线程安全的原理(理论层面)

万物皆对象,你只要这里指定一个对象,那么,就可以作为一把锁。我们比如说就来指定,除了我们想指定我们的实例对象以外,本例中我们也可以指定Sequence.class,Sequence.class也是一个对象,它也可以作为锁,

14-synchronized保证线程安全的原理(理论层面)

后面我们会讲一个关于锁重入的概念,当我们锁的这个对象,锁的是一个对象,那么,所有持有这个对象的线程都可以进入,那么,如果锁的是一个字节码文件对象,那么,这个字节码,我们知道,是在全局唯一的,那么,也就是说

14-synchronized保证线程安全的原理(理论层面)

在这里也会出现所谓的锁重入的问题。

好了,这是关于synchronized的语法,这样呢,关于synchronized的了解基本上就差不多了。其实synchronized的原理其实就是加了锁,内置锁和互斥锁所决定的。每个对象都有锁,而这些锁都是互斥的,一个进来之后,另外的就不能进来了,因此就可以保证线程的安全性。

14-synchronized保证线程安全的原理(理论层面)

那么关于内置锁和互斥锁的内容就说完了,下面从Java虚拟机,从字节码文件的角度,来深入的了解synchronized的原理,本讲就到这里。