java中有哪些锁
这个问题在我看了一遍<java并发编程>后尽然无法回答,说明自己对于锁的概念了解的不够。于是再次翻看了一下书里的内容,突然有点打开脑门的感觉。看来确实是要学习的最好方式是要带着问题去学,并且解决问题。
在java中锁主要两类:内部锁synchronized和显示锁java.util.concurrent.locks.Lock。但细细想这貌似总结的也不太对。应该是由java内置的锁和concurrent实现的一系列锁。
为什么这说,因为在java中一切都是对象,而java对每个对象都内置了一个锁,也可以称为对象锁/内部锁。通过synchronized来完成相关的锁操作。
而因为synchronized的实现有些缺陷以及并发场景的复杂性,有人开发了一种显式的锁,而这些锁都是由java.util.concurrent.locks.Lock派生出来的。当然目前已经内置到了JDK1.5及之后的版本中。
synchronized
首先来看看用的比较多的synchronized,我的日常工作中大多用的也是它。synchronized是用于为某个代码块的提供锁机制,在java的对象中会隐式的拥有一个锁,这个锁被称为内置锁(intrinsic)或监视器锁(monitor locks)。线程在进入被synchronized保护的块之前自动获得这个锁,直到完成代码后(也可能是异常)自动释放锁。内置锁是互斥的,一个锁同时只能被一个线程持有,这也就会导致多线程下,锁被持有后后面的线程会阻塞。正因此实现了对代码的线程安全保证了原子性。
可重入
既然java内置锁是互斥的而且后面的线程会导致阻塞,那么如果持有锁的线程再次进入试图获得这个锁时会如何呢?比如下面的一种情况:
public class BaseClass {
public synchronized void do() {
System.out.println("is base");
}
}
public class SonClass extends BaseClass {
public synchronized void do() {
System.out.println("is son");
super.do();
}
}
SonClass son = new SonClass();
son.do();
此时派生类的do方法除了会首先会持有一次锁,然后在调用super.do()的时候又会再一次进入锁并去持有,如果锁是互斥的话此时就应该死锁了。
但结果却不是这样的,这是因为内部锁是具有可重入的特性,也就是锁实现了一个重入机制,引用计数管理。当线程1持有了对象的锁a,此时会对锁a的引用计算加1。然后当线程1再次获得锁a时,线程1还是持有锁a的那么计算会加1。当然每次退出同步块时会减1,直到为0时释放锁。
synchronized的一些特点
修饰代码的方式
- 修饰方法
public class BaseClass { public synchronized void do() { System.out.println("is base"); } }
这种就是直接对某个方法进行加锁,进入这个方法块时需要获得锁。
- 修饰代码块
public class BaseClass { private static Object lock = new Object(); public void do() { synchronized (lock) { System.out.println("is base"); } } }
这里就将锁的范围减少到了方法中的部分代码块,这对于锁的灵活性就提高了,毕竟锁的粒度控制也是锁的一个关键问题。
对象锁的类型
经常