目录
正文:
Synchronized和Lock的区别
synchronized
关键字和java.util.concurrent.locks.Lock
都能加锁,两者有什么区别呢?
- 原始构成:
sync
是JVM层面的,底层通过monitorenter
和monitorexit
来实现的。Lock
是JDK API层面的。(sync
一个enter会有两个exit,一个是正常退出,一个是异常退出) - 使用方法:
sync
不需要手动释放锁,而Lock
需要手动释放。 - 是否可中断:
sync
不可中断,除非抛出异常或者正常运行完成。Lock
是可中断的,通过调用interrupt()
方法。 - 是否为公平锁:
sync
只能是非公平锁,而Lock
既能是公平锁,又能是非公平锁。 - 绑定多个条件:
sync
不能,只能随机唤醒。而Lock
可以通过Condition
来绑定多个条件,精确唤醒。
synchronized&ReentrantLock
synchronized和ReentrantLock都称为可重入锁,也称递归锁. 指的同一个线程在外层方法获得锁时,进入内层方法会自动获取锁。也就是说,线程可以进入任何一个它已经拥有锁的代码块。比如get
方法里面有set
方法,两个方法都有同一把锁,得到了get
的锁,就自动得到了set
的锁。
就像有了家门的锁,厕所、书房、厨房就为你敞开了一样。可重入锁可以避免死锁的问题。
代码如下:
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone=new Phone();
syncTest(phone);
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t1=new Thread(phone);
Thread t2=new Thread(phone);
t1.start();
t2.start();
}
private static void syncTest(Phone phone) {
new Thread(()->{
try{
phone.sendSMS();
}catch (Exception e){
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try{
phone.sendSMS();
}catch (Exception e){
e.printStackTrace();
}
},"t2").start();
}
}
class Phone implements Runnable{
//Synchronized TEST
public synchronized void sendSMS(){ System.out.println(Thread.currentThread().getId()+"\t"+"sendSMS()");
sendEmail();
}
public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getId()+"\t"+"sendEmail()");
}
//Reentrant TEST
Lock lock=new ReentrantLock();
@Override
public void run() {
get();
}
public void get(){
//lock.lock();//mark1
lock.lock();
try{
System.out.println(Thread.currentThread().getId()+"\t"+"get()");
set();
}finally {
//lock.unlock();//mark2
lock.unlock();
}
}
public void set(){
lock.lock();//mark3
try{
System.out.println(Thread.currentThread().getId()+"\t"+"set()");
}finally {
lock.unlock();//mark4
}
}
}
运行结果:
12 sendSMS()
12 sendEmail()
13 sendSMS()
13 sendEmail()
14 get()
14 set()
15 get()
15 set()
由以上代码可知, synchronized和ReentrantLock都能实现线程同步,保证结果的一致性.
思考:可以加多个锁或解多个锁吗?
可以, 但加锁的数量和解锁的数量必须相等。
这个问题在阿里巴巴的面试中有被问到,不妨我们来用代码证明一下。
在注释掉以上的代码的mark1和mark2时,运行的结果正常如下:
14 get()
14 set()
15 get()
15 set()
(1).当将mark1和mark2同时打开时
结果同上
(2).当打开mark1关闭mark2时
12 get()
12 set()
编译通过,但只出来第1个线程的结果, 相当于第二个线程被阻塞, 编译还在进行。
这是因为第1个线程的锁还没有被释放,导致第2个线程无法加锁。而导致第2个线程处于的一个等待的状态。
(3).当关闭mark1打开mark2时
程序会正常执行完毕,但是会报一个异常(非法监视器异常)。
12 get()
12 set()
13 get()
13 set()
Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at
...
这个情况是由一个没有被加锁的线程而强制释放一个所导致的。
(4).当关闭mark1, 打开mark2, 注释掉mark4时
这时我们会发现程序照样执行正常完毕, 那是因为mark2处的锁释放了mark3处的锁, 从而不会抛出异常。
13 get()
13 set()
12 get()
12 set()
通过分析以上4种情况,我们大致可以得出一个规律: 当一个锁加加锁的个数和解锁的个数数量相等的时候,无论是否在一个方法里面, 程序都能正常执行完毕, 能保证结果的一致性和正确性。但在一般情况下,对于一个线程或一个方法,通常只加一个锁和解一个锁。