java 锁

一段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 虚拟机都会自动锁上对象或者类。

一个简单的锁

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()方法才能释放锁, 这为获取锁和释放锁不出现在同一个块结构中, 以及以更自由的顺序释放锁提供了可能. 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值