Java中,锁分为内部锁与显式锁两种类型。
内部锁又分为对象锁与类锁。
Java中,每个类的java对象内部都有一个锁,称为对象锁;每个类都有一个唯一的class对象,class对象内部锁,称为类锁,主要用来同步静态方法与静态代码块。两者都可以直接在方法前使用synchronized修饰来获得,也可以在代码块上使用synchronize(this)来获取对象锁,使用synchronized(Class.class)来获得类锁。(Class.class为类名)
对象锁与对象的属性,方法是没有什么内在联系的,所以使用某个对象的内部锁时,完全可以对其进行其他操作。
因为类锁只有一个,所以所有类的对象实例共享。
JDK5.0之后,Java中加入了java.util.concurrent包,其中的子包locks里面提供了显式锁,ReentrantLock和ReentrantReadWriteLock。显式锁比起内部锁要更加灵活方便与多样,提供了定时和轮询的功能,此外还加入了读写锁,即ReentrantReadWriteLock。
ReentrantLock实现了lock接口
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();Condition newCondition();
}
对于内部锁,synchronized方法,如果获取不到锁,线程便会阻塞,一直等待。这与显式锁的lock()方法一致。
显示锁还提供了lockInterruptibly方法。当线程因为获取内部锁而阻塞时,对该线程的中断也会产生阻塞,当线程最终获得了锁之后,线程的阻塞才会生效。而lockInterruptibly方法便可以让线程在等待获取锁时,也能顾响应中断。
显示锁中的trylock方法可以对锁的占用情况进行询问,并立即返回结果。如果该锁当前空闲,便会返回true,并获取锁;若锁当前被占用,则返回false。同时,trylock方法还实现了定时功能,在指定时间内不断对锁的占用情况进行询问,返回结果同上。第一个参数为时长,第二个参数为时间单位。
在内部锁中,一旦程序执行结束,系统将自动解锁,但在显示锁中,需要调用unlock方法进行手动解锁。
此外,显示锁还提供了Condition来丰富lock的功能。每一个Condition都相当于一个阻塞队列。
内部锁中,使用wait(),notify()/notifyAll()方法对线程进行阻塞和唤醒来实现锁的控制,在显示锁中,使用condition的await(),signal()和signalAll()方法来实现对锁的控制。
显式锁实现的生产者-消费者线程:
public class Queue {
private int maxsize = 10;
private LinkedList<Integer> storage = new LinkedList<>();
volatile int count = 0;
private ReentrantLock reentrantLock = new ReentrantLock();
private Condition empty = reentrantLock.newCondition();
private Condition full = reentrantLock.newCondition();
Queue() {
}
public void put() {
reentrantLock.lock();
try {
while (storage.size() == maxsize) {
System.out.println(Thread.currentThread().getName() + ": wait\n");
full.await();
}
count ++;
System.out.println(Thread.currentThread().getName() + ": add " + count + "\n");
storage.add(count);
Thread.sleep(1000);
empty.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void take() {
reentrantLock.lock();
try {
while (storage.size() == 0) {
System.out.println(Thread.currentThread().getName() + ": wait\n");
empty.await();
}
System.out.println(Thread.currentThread().getName() + ": remove" + storage.getFirst() + "\n");
storage.removeFirst();
Thread.sleep(1000);
full.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
内部锁实现的生产者消费者线程:
public class Queue {
private int maxsize = 10;
private LinkedList<Integer> storage = new LinkedList<>();
volatile int count = 0;
Queue() {
}
public synchronized void put() {
try {
while (storage.size() == maxsize) {
System.out.println(Thread.currentThread().getName() + ": wait\n");
wait();
}
count ++;
System.out.println(Thread.currentThread().getName() + ": add " + count + "\n");
storage.add(count);
Thread.sleep(1000);
notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void take() {
try {
while (storage.size() == 0) {
System.out.println(Thread.currentThread().getName() + ": wait\n");
wait();
}
System.out.println(Thread.currentThread().getName() + ": remove" + storage.getFirst() + "\n");
storage.removeFirst();
Thread.sleep(1000);
notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
java.util.concurrent包中还引入了读写锁,ReadWriteLock。ReadWriteLock分为ReadLock和WriteLock,当使用读锁锁住时,其他所有的读锁都可重入,而写锁不可入。当写锁锁住时,其他所有的锁,无论写锁还是读锁,都不可重入。如果没有写锁的情况下,读是没有阻塞的,这在一定程度上,提高了程序的效率。