Volatile关键字:
Volatile关键字作用:
(1)volatile关键字的作用就是保证了可见性和有序性(不保证原子性),如果一个共享变量被volatile关键字修饰,那么如果一个线程修改了这个共享变量后,其他线程是立马可知的。线程A修改了共享变量副本,此时如果该变量被volatile修饰,那么本次修改结果会立即刷新到主存中.
(2)volatile禁止指令重排序优化,保证了有序性。
(3)volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令
1、volatile
关键字能保证数据的可见性,但不能保证数据的原子性。synchronized
关键字两者都能保证。
2、在 Java 中,volatile
关键字除了可以保证变量的可见性,还有一个重要的作用就是防止 JVM 的指令重排序。 如果我们将变量声明为 volatile
,在对这个变量进行读写操作的时候,会通过插入特定的 内存屏障 的方式来禁止指令重排序。
3、volatile
关键字能保证变量的可见性,但不能保证对变量的操作是原子性的。
synchronized关键字:
synchronized关键字的作用:
(1)volatile只能作用于变量,使用范围较小。synchronized可以用在变量、方法、类、同步代码块等,使用范围比较广。
(2)volatile只能保证可见性和有序性,不能保证原子性。而可见性、有序性、原子性synchronized都可以包证。
(3)volatile不会造成线程阻塞。synchronized可能会造成线程阻塞。
(4)在性能方面synchronized关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。
synchronized锁(对象锁、模板锁)
--> 当一个对象的不同synchronized方法,拿到的是同一把锁
--> 当加了static 的synchronized方法,是多个对象之间的一把锁
两把锁不一样,相互之间没有影响
1、一个对象,一个synchronized方法,一个对象,普通方法无关锁
2、多个对象,一个synchronized方法,多把锁
3、多个对象,多个synchronized方法,多把锁
4、一个对象,多个synchronized方法是一个锁,普通方法无关
5、多个对象,多个static synchronized方法,锁了模板,成了一个锁
6、单个对象,一个synchronized方法,一个static synchronized方法,不同的两把锁,一个模板锁、一个对象锁,相互不影响
7、两个对象,一个synchronized方法,一个static synchronized方法,不同的三把锁,一个模板锁,两个对象锁
synchronized底层原理:
synchronized
修饰同步语句块的实现使用的是 monitorenter
和 monitorexit
指令,其中 monitorenter
指令指向同步代码块的开始位置,monitorexit
指令则指明同步代码块的结束位置。
synchronized
修饰方法的时候并没有 monitorenter
指令和 monitorexit
指令,取得代之的确实是 ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法。
不过两者的本质都是对对象监视器 monitor 的获取。
synchronized优化:
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
synchronized在jdk1.6之后为什么要加入锁升级的机制?
答案:因为在之前的版本中synchronized一定是重量级锁,而重量级锁时需要通过内核态去实现的,而用户态到内核态的切换很费时,但在程序具体执行中synchronized修饰的对象在线程争抢不激烈时并不需要重量级锁,所以加入锁升级机制后,偏向锁和轻量级锁在代码层面就可以解决,不需要进入内核态处理。
synchronized锁升级过程记录锁状态就是在对象头中
【偏向锁】:
markWord中放的是当前线程指针,所谓的偏向锁是没有必要设计锁竞争机制的,第一个访问这把锁的线程直接把自己的线程ID往上一贴就完了。总而言之是贴上当前线程的标志。
【自旋锁】:
先撤销偏向锁之前贴上的线程标识 ,撤销掉之后进行竞争 ,竞争的方式就是——自旋的竞争 ;
LockRecord
(sychronized是可重入锁,不同重量级记录不一样)
1、偏向锁记录在线程栈里,每增加一次,LR+1,偏向锁、自旋锁 -> 线程栈 -> LR+1
2、重量级锁 -> ObjectMonitor 字段上。
每个线程在它的线程栈中生成一个LR(锁记录),将这个LR贴到锁上,锁上的指针指向哪一个线程的LR , 就表示哪个线程持有这把锁。另外的线程只能用CAS机制继续竞争。
【重量级锁】:
//我这把锁必须得向操作系统去申请;MarkWord中实际记录的是一个ObjectMoint——实际就是JVM空间写的一个C++对象 , 而这个C++对象它内部去访问的时候是需要通过操作系统,经过操作系统之后拿到操作系统对应的那一把锁。
【synchronized编译过程】:
当我们在Java文件中写了synchronized{…} 代码块之后,在汇编语句中就会出现:
monitorenter——原 { 位置处,锁开始。
monitorexit—–—原 } 位置出,锁结束。
最后一个monitorexit—–—产生任何异常的话。
synchronized 和 volatile 有什么区别?
synchronized
关键字和 volatile
关键字是两个互补的存在,而不是对立的存在!
volatile
关键字是线程同步的轻量级实现,所以volatile
性能肯定比synchronized
关键字要好 。但是volatile
关键字只能用于变量而synchronized
关键字可以修饰方法以及代码块 。volatile
关键字能保证数据的可见性,但不能保证数据的原子性。synchronized
关键字两者都能保证。volatile
关键字主要用于解决变量在多个线程之间的可见性,而synchronized
关键字解决的是多个线程之间访问资源的同步性。
ReentrantLock(可重入锁)
ReentrantLock
实现了 Lock
接口,是一个可重入且独占式的锁,和 synchronized
关键字类似。不过,ReentrantLock
更灵活、更强大,增加了轮询、超时、中断、公平锁和非公平锁等高级功能。
ReentrantLock
里面有一个内部类 Sync
,Sync
继承 AQS(AbstractQueuedSynchronizer
),添加锁和释放锁的大部分操作实际上都是在 Sync
中实现的。Sync
有公平锁 FairSync
和非公平锁 NonfairSync
两个子类。
ReentrantLock
默认使用非公平锁,也可以通过构造器来显式的指定使用公平锁。 从上面的内容可以看出, ReentrantLock
的底层就是由 AQS 来实现的。
synchronized 和 ReentrantLock 有什么区别?
1、两者都是可重入锁
可重入锁 也叫递归锁,指的是线程可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果是不可重入锁的话,就会造成死锁。
2、synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
synchronized
是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized
关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。
ReentrantLock
是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
3、ReentrantLock 比 synchronized 增加了一些高级功能
相比synchronized
,ReentrantLock
增加了一些高级功能。主要来说主要有三点:
- 等待可中断 :
ReentrantLock
提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()
来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 - 可实现公平锁 :
ReentrantLock
可以指定是公平锁还是非公平锁。而synchronized
只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock
默认情况是非公平的,可以通过ReentrantLock
类的ReentrantLock(boolean fair)
构造方法来制定是否是公平的。 - 可实现选择性通知(锁可以绑定多个条件):
synchronized
关键字与wait()
和notify()
/notifyAll()
方法相结合可以实现等待/通知机制。ReentrantLock
类当然也可以实现,但是需要借助于Condition
接口与newCondition()
方法。
4、synchronized
就属于是不可中断锁、ReentrantLock
就属于是可中断锁。
- 可中断锁:获取锁的过程中可以被中断,不需要一直等到获取锁之后 才能进行其他逻辑处理。
ReentrantLock
就属于是可中断锁。 - 不可中断锁:一旦线程申请了锁,就只能等到拿到锁以后才能进行其他的逻辑处理。
synchronized
就属于是不可中断锁。