锁的目的让一系列操作能被当作“原子”,能够正确执行。锁分为悲观锁和乐观锁,悲观锁就是假设一个线程在执行某些操作时(比如读写数据)别的线程也一定会一定会执行这些操作,叫并发冲突,所以在执行前先上锁,只能当前线程先操作,别的线程要操作时发现有锁先阻塞。乐观锁就是,假设不会有并发冲突,线程该执行什么操作就执行什么操作,只有更新的时候,才会检查有没有冲突,如果有冲突,可以让用户决定怎么处理,或者重试。乐观锁是一种实现思想。
java中的synchronized就是悲观锁,CAS机制就是乐观锁,比如AtomicInteger。
还有一点需要说明一下,锁只是保证了原子操作,其它和这个锁没有关系的线程该执行还是执行,不受影响。
在多线程编程中有一个同步的概念,比如多个线程都需要读写一个变量,这时就需要注意了,此时就会出现线程不安全的问题,导致意想不到的结果。java内存模型有可见性、原子性和有序性三性,同步就是围绕这三特性展开的。
我们声明一个变量,比如int a; 对这个进行自增操作 a++。此时的a,既没有可见性,也没有原子性和有序性。有两个线程A和B,分别对a,自增,我们假设这两个线程分别由两个CPU执行。每个CPU都有自己高速缓存,这样的话A和B线程都会缓存a,对a操作完再写回到主内存,如果A线程修改了a,可是线程B是不知道的,它缓存的a变量还是旧的。这就说明了A线程对a的修改对B线程是不可见的,这样就会出现问题。我们可以使用synchronized、volatile和final关键字让a有可见性,volatile可见性具体原理看参考的文章。我决定synchronized实现可见性很直接,就是上锁后,只能这个线程读a,所以不会出现上面说的问题。
volatile int a; 这样a就是具有了可见性,可是还是没有原子性(volatile只具有可见性和有序性),在A和B线程中分别执行a++,还是会有问题,那么我们就需要用乐观锁来保证原子性了。看看AtomicInteger的源码,就是volatile加乐观锁实现的。
synchronized由于加锁的原因,本身就保证了可见性、原子性和有序性。
Volatile关键字
volatile是一种稍弱的同步机制,它实现了可见性和有序性,可见性是通过在给volatile修饰的变量赋值的时候,插入一条指令,让后通过cpu的EMSI协议和嗅探机制实现的;有序性是通过插入内存屏障指令实现的,也就是编译器或者执行的时候禁止对指令重排序优化。
总结一下:
synchronized是悲观锁,实现了可见性、原子性和有序性,可以实现多线程环境的同步。
volatile实现了可见性和有序性,需要和乐观锁思想配合使用,才能实现完整的同步机制。