Java之synchronize锁和lock锁
Java之synchronize锁和lock锁
Java中多线程主要两种加锁机制:
- Synchronize
- 显示Lock
Synchronized锁
1.1synchronized锁是什么?
synchronized是Java的一个关键字,它能够将代码块(方法)锁起来。使用起来非常简单,只要在代码块(方法)添加关键字synchronized即可实现同步功能。
1
2
3public synchronized void test() {
......
}
synchronized是一种互斥锁,一次只能允许一个线程进入被锁的代码块
synchronized是一种内置锁/监视器锁,Java中每个对象都有一个内置锁(监视器,也可以理解成锁标记),而synchronized就是使用对象的内置锁(监视器)来将代码块(方法)锁定的!(锁的是对象,但我们同步的是方法/代码块)
1.2synchronize用处是什么?
- synchronized保证了线程的原子性。(被保护的代码块是一次被执行的,没有任何线程会同时访问)
- synchronized还保证了可见性。(当执行完synchronized之后,修改后的变量对其他的线程是可见的)
Java中的synchronized,通过使用内置锁,来实现对变量的同步操作,进而实现了对变量操作的原子性和其他线程对变量的可见性,从而确保了并发情况下的线程安全。
1.3synchronized的原理
synchronized修饰方法和代码块的代码:
1 | public class Main { |
代码编译后,进行反编译一下:
同步代码块:
- monitorenter和monitorexit指令实现的
同步方法(在这看不出来,需要看JVM底层实现)
- 方法修饰符上的ACC_SYNCHRONIZED实现
synchronized底层是通过monitor对象,对象有自己的对象头,存储了很多信息,其中一个信息标识是被哪个线程持有。
1.4synchronized如何使用
synchronized一般用来修饰三种东西:
- 修饰普通方法
- 修饰代码块
- 修饰静态方法
1.4.1修饰普通方法
Test对象(内置锁)
1 | public class Test { |
1.4.2修饰代码块
Test对象(内置锁)–> this
1 | public class Test { |
使用synchronized修饰代码块时未必使用this,还可以使用其他的对象(随便一个对象都有一个内置锁),代码如下:
1 | public class Test { |
这种方式(随便使用一个对象作为锁)被称为–>客户端锁,这是不建议使用的。
1.4.3修饰静态方法
获取到的是类锁(类的字节码文件对象):Test.class
1 | public class Test { |
1.4.4类锁与对象锁
synchronized修饰静态方法获取的是类锁(类的字节码文件对象),synchronized修饰普通方法或代码块获取的对象锁。
类锁和对象锁是不冲突的,也就是说:获取了类锁的线程和获取了对象锁的线程是不冲突的!
1 | package com.thread; |
结果证明:类锁和对象锁是不会冲突的!
1.5重入锁
先看下面的代码:
1 | public class Widget { |
- 当线程A进入到LoggingWidget的
doSomething()
方法时,此时拿到了LoggingWidget实例对象的锁。 - 随后在方法上又调用了父类Widget的
doSomething()
方法,它又是被synchronized修饰。 - 那现在我们LoggingWidget实例对象的锁还没有释放,进⼊⽗类Widget的 doSomething() ⽅法还需要⼀把锁吗?– 不需要
因为锁的持有者是“线程”,⽽不是“调⽤”。线程A已经是有了LoggingWidget实例对象的锁了,当再需要的时候可以继续“开锁”进去的!
这就是内置锁的可重入性。记住,持有锁的是线程。
1.6释放锁的时机
- 当方法(代码块)执行完毕后会自动释放锁,不需要任何操作。
- 当一个线程执行的代码出现异常时,其所持有的锁会自动释放。不会由于异常导致出现死锁现象。
Lock显示锁
2.1Lock显示锁简单介绍
Lock显示锁是JDK1.5之后有的,之前都是使用synchronized锁来使线程安全。
Lock显示锁是一个接口:
看下Lock的注释的介绍
1 | /** |
可以简单概括下:
- Lock方式来获取锁支持中断、超时不获取、是非阻塞的
- 提高了语义化,哪里加锁,哪里解锁都得写出来
- Lock显示锁可以给我们带来很好的灵活性,但同时我必须手动释放锁
- 支持Condition条件对象
- 允许多个读线程同时访问共享资源
2.2synchronized锁和Lock锁使用哪个
从上面的介绍,Lock显示锁给我们的程序带来了很多的灵活性,很多特性都是synchronized锁没有的。那我们只使用Lock显示锁吗?
自然不是,Lock锁在刚出来的时候很多性能方面都比synchronized锁要好,但是从jdk1.6开始synchronized锁就做了各种优化(synchronized是Jdk原生的)
优化操作:适应自旋锁,锁消除,锁粗化,轻量级锁,偏向锁。
所以,到现在Lock锁和Synchronized锁的性能其实差别不是很⼤!⽽Synchronized锁⽤起来⼜特别简单。 Lock锁还得顾忌到它的特性,要⼿动释放锁才⾏(如果忘了释放,这就是⼀个隐患)
所以说,我们绝⼤部分时候还是会使⽤Synchronized锁,⽤到了Lock锁提及的特性,带来的灵活性才会考虑使⽤Lock显式锁
2.3公平锁
- 公平锁理解起来⾮常简单:线程将按照它们发出请求的顺序来获取锁
- ⾮公平锁就是:线程发出请求的时可以“插队”获取锁
- Lock和synchronize都是默认使⽤⾮公平锁的。如果不是必要的情况下,不要使⽤公平锁
- 公平锁会来带⼀些性能的消耗的