一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的锁(一个对象只有一把锁); 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池等待队列中)。 取到锁后,他就开始执行同步代码(被synchronized修饰的代码);线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中等待的某个线程就可以拿到锁执行同步代码了。这样就保证了同步代码在统一时刻只有一个线程在执行。
在Java程序运行时环境中,JVM需要对两类线程共享的数据进行协调:
1)保存在堆中的实例变量
2)保存在方法区中的类变量
这两类数据是被所有线程共享的。
(程序不需要协调保存在Java 栈当中的数据。因为这些数据是属于拥有该栈的线程所私有的。)
在java虚拟机中,每个对象和类在逻辑上都是和一个监视器相关联的。
对于对象来说,相关联的监视器保护对象的实例变量。
对于类来说,监视器保护类的类变量。
(如果一个对象没有实例变量,或者一个类没有变量,相关联的监视器就什么也不监视。)
为了实现监视器的排他性监视能力,java虚拟机为每一个对象和类都关联一个锁。代表任何时候只允许一个线程拥有的特权。线程访问实例变量或者类变量不需锁。
但是如果线程获取了锁,那么在它释放这个锁之前,就没有其他线程可以获取同样数据的锁了。(锁住一个对象就是获取对象相关联的监视器)
类锁实际上用对象锁来实现。当虚拟机装载一个class文件的时候,它就会创建一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。
一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。
java编程人员不需要自己动手加锁,对象锁是java虚拟机内部使用的。
在java程序中,只需要使用synchronized块或者synchronized方法就可以标志一个监视区域。当每次进入一个监视区域时,java 虚拟机都会自动锁上对象或者类。
1)保存在堆中的实例变量
2)保存在方法区中的类变量
这两类数据是被所有线程共享的。
(程序不需要协调保存在Java 栈当中的数据。因为这些数据是属于拥有该栈的线程所私有的。)
在java虚拟机中,每个对象和类在逻辑上都是和一个监视器相关联的。
对于对象来说,相关联的监视器保护对象的实例变量。
对于类来说,监视器保护类的类变量。
(如果一个对象没有实例变量,或者一个类没有变量,相关联的监视器就什么也不监视。)
为了实现监视器的排他性监视能力,java虚拟机为每一个对象和类都关联一个锁。代表任何时候只允许一个线程拥有的特权。线程访问实例变量或者类变量不需锁。
但是如果线程获取了锁,那么在它释放这个锁之前,就没有其他线程可以获取同样数据的锁了。(锁住一个对象就是获取对象相关联的监视器)
类锁实际上用对象锁来实现。当虚拟机装载一个class文件的时候,它就会创建一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。
一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。
java编程人员不需要自己动手加锁,对象锁是java虚拟机内部使用的。
在java程序中,只需要使用synchronized块或者synchronized方法就可以标志一个监视区域。当每次进入一个监视区域时,java 虚拟机都会自动锁上对象或者类。
一个简单的锁
public class Counter {
private int count = 0;
public int inc() {
synchronized (this) {
return ++count;
}
}
}
如果用Lock关键字实现则是这个样子
public class SimpleLock {
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException {
while (isLocked) {
System.out.println("锁已被获取,等待中...");
wait();
}
isLocked = true;
}
public synchronized void unlock() {
isLocked = false;
notify();
}
}
public class Counter {
private Lock lock = new Lock();
private int count = 0;
public int inc() {
lock.lock();
int newCount = ++count;
lock.unlock();
return newCount;
}
}
二、可重入锁
其实意思就是假如有两个方法,你持有了其中的一个锁,而令一个方法也是用的这个锁,那么你可以进入第二个方法,
代码:
public class Reentrant {
public synchronized void outer() {
inner();
}
public synchronized void inner() {
System.out.println("===========");
}
public static void main(String[] args) {
Reentrant rt = new Reentrant();
rt.outer();
}
}
这个时候你调用 outer方法,你得到了这个对象的锁,你在outer() 方法中是可以执行inner方法的,为什么?因为inner方法的锁就是这个对象,而你已经获得了。让我们来修改一下上面的 SimpleLock 类,把他变成一下可重入的锁吧
public class ReentrantLock {
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock() throws InterruptedException {
Thread callingThread = Thread.currentThread();
while (isLocked && lockedBy != callingThread) {
wait();
}
isLocked = true;
lockedCount++;
lockedBy = callingThread;
}
public synchronized void unlock() {
if (Thread.currentThread() == this.lockedBy) {
lockedCount--;
if (lockedCount == 0) {
isLocked = false;
notify();
}
}
}
}
类很简单,不需要多说 ,就是判断了一下获取当前锁的对象是不是当前锁的持有人,如果是,就依然能执行lock() 方法后的代码块。
测试方法运行的看看
public class TestMain {
SimpleLock lock = new SimpleLock();
public void outer() throws InterruptedException {
lock.lock();
inner();
lock.unlock();
}
public synchronized void inner() throws InterruptedException {
lock.lock();
System.out.println("===================");
lock.unlock();
}
public static void main(String[] args) throws InterruptedException {
TestMain rt = new TestMain();
rt.outer();
}
}
使用简单锁,outer方法中调用inner方法会wait ,看看 SimpleLock 的代码就知道为什么了,因为每次调用时都会先调用 lock() 方法而此时lock方法已经被调用过,并还没有unlock()。
三、公平锁
概念就不解释了。java中的synchronized关键字不能保证线程执行的公平性,相对来说使用 Lock来做同步会比用 同步关键字更公平一点。我们先来看一段代码public class Synchronizer {
public synchronized void doSynchronized() {
// do a lot of work which takes a long time
}
}
假设有A B C 3个线程调用该方法,当前在执行的是A线程,其他的会等待,此时A线程执行完后,B先抢占了,B开始执行,这时A又要执行该方法,有一种可能情况是A B线程的优先级或其他原因,导致C线程一直得不到执行机会。下面的方法会相对公平些:
public class Synchronizer {
Lock lock = new Lock();
public void doSynchronized() throws InterruptedException {
this.lock.lock();
// critical section, do a lot of work which takes a long time
this.lock.unlock();
}
}
public class Lock {
private boolean isLocked = false;
private Thread lockingThread = null;
public synchronized void lock() throws InterruptedException {
while (isLocked) {
wait();
}
isLocked = true;
lockingThread = Thread.currentThread();
}
public synchronized void unlock() {
if (this.lockingThread != Thread.currentThread()) {
throw new IllegalMonitorStateException(
"Calling thread has not locked this lock");
}
isLocked = false;
lockingThread = null;
notify();
}
}
为什么比同步关键字更公平,因为同步关键字是线程间的竞争,而Lock对每个线程都是同等对待,他通过notify()来决定接下来执行的是那个线程。也许你觉得这个也不怎么公平,应为你依然不确定线程执行的优先规则,那么下面的例子应该会让你觉得更公平。
import java.util.ArrayList;
import java.util.List;
public class FairLock {
private boolean isLocked = false;
private Thread lockingThread = null;
private List<QueueObject> waitingThreads = new ArrayList<QueueObject>();
public void lock() throws InterruptedException {
QueueObject queueObject = new QueueObject();
boolean isLockedForThisThread = true;
synchronized (this) {
waitingThreads.add(queueObject);
}
while (isLockedForThisThread) {
synchronized (this) {
isLockedForThisThread = isLocked
|| waitingThreads.get(0) != queueObject;
if (!isLockedForThisThread) {
isLocked = true;
waitingThreads.remove(queueObject);
lockingThread = Thread.currentThread();
return;
}
}
try {
queueObject.doWait();
} catch (InterruptedException e) {
synchronized (this) {
waitingThreads.remove(queueObject);
}
throw e;
}
}
}
public synchronized void unlock() {
if (this.lockingThread != Thread.currentThread()) {
throw new IllegalMonitorStateException(
"Calling thread has not locked this lock");
}
isLocked = false;
lockingThread = null;
if (waitingThreads.size() > 0) {
waitingThreads.get(0).doNotify();
}
}
}
public class QueueObject {
private boolean isNotified = false;
public synchronized void doWait() throws InterruptedException {
while (!isNotified) {
this.wait();
}
this.isNotified = false;
}
public synchronized void doNotify() {
this.isNotified = true;
this.notify();
}
public boolean equals(Object o) {
return this == o;
}
}
代码很简单,大家都看的出来,只是用了一个List来保存所有想执行的线程,这当然会比普通锁效率低,但他能让你知道是通过什么规则来分配执行资源的,这里就是先到先执行,你也可以实现自己更复杂的排序算法。不过想要说明一下的是 java中不太可能能保证绝对的线程公平。
四、别忘了在finally中调用unlock
lock.lock();
try{
//do something
} finally {
lock.unlock();
}
高级
关于互斥锁:
所谓互斥锁, 指的是一次最多只能有一个线程持有的锁. 在jdk1.5之前, 我们通常使用synchronized机制控制多个线程对共享资源的访问. 而现在, Lock提供了比synchronized机制更广泛的锁定操作, Lock和synchronized机制的主要区别:
synchronized机制提供了对与每个对象相关的隐式监视器锁的访问, 并强制所有锁获取和释放均要出现在一个块结构中, 当获取了多个锁时, 它们必须以相反的顺序释放. synchronized机制对锁的释放是隐式的, 只要线程运行的代码超出了synchronized语句块范围, 锁就会被释放. 而Lock机制必须显式的调用Lock对象的unlock()方法才能释放锁, 这为获取锁和释放锁不出现在同一个块结构中, 以及以更自由的顺序释放锁提供了可能.