synchronized
先来看下利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式
-
对于普通同步方法,锁是当前实例对象
-
对于静态同步方法,锁是当前类的Class对象
-
对于同步方法块,锁是synchonized括号里配置的对象
锁升级
JAVA对象头
Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位,这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化
-
64位
- 32位
四种锁状态对应的的Mark Word内容
锁状态 | 存储内容 | 存储内容 |
---|---|---|
无锁 | 对象的hashCode、对象分代年龄、是否是偏向锁(0) | 01 |
偏向锁 | 偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1) | 01 |
轻量级锁 | 指向栈中锁记录的指针 | 00 |
重量级锁 | 指向互斥量(重量级锁)的指针 | 10 |
锁升级
Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁
-
无锁
无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功 -
偏向锁
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价 -
轻量级锁
是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能 -
重量级锁
升级为重量级锁时,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态
图示
悲观锁 VS 乐观锁
-
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作,Java中,synchronized关键字和Lock的实现类都是悲观锁
-
乐观锁:假定不会发生并发冲突,只在提交操作时检测是否违反数据完整性,Java中,类似AtomicInteger、AtomicLong等是乐观锁
CAS
Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁
CAS算法涉及到三个操作数:
-
需要读写的内存值 V
-
进行比较的值 A
-
要写入的新值 B
当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作
下面通过AtomicInteger举例
加减都是通过getAndAddInt里面的CAS处理
CAS存在的问题
-
ABA问题
CAS是通过比较值是否相等来执行,假如原始值为A,一个线程先修改为B,另一个线程修改为A,判断相等,就会更新,为了解决这个问题使用了版本号,1A >> 2B >> 3A -
循环时间长开销大
CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销
公平锁 VS 非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大
非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁
-
公平锁
-
非公平锁
-
主要区别是hasQueuedPredecessors方法,判断了是否是第一个
-
可重入锁 VS 不可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞
synchronized是可重入锁 -
ReentrantLock是可重入锁,如果当前线程已经持有了,state值加1
-
不可重入锁,没有找到具体类,自己随便实现了一个,一上来直接就CAS
-
共享锁 VS 独享锁
共享锁是指该锁可被多个线程所持有。如果线程1对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据
独享锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据,如JDK中的synchronized和JUC中Lock的实现类
-
ReentrantReadWriteLock 的读锁和写锁
-
写锁过程
-
读锁过程
-