Java中每一个对象都有1个内置锁,当程序运行到非静态的synchronized同步方法上时,自动取得与正在履行代码类确当前实例(this实例)有关的锁。取得1个对象的锁也称为获得锁、锁定对象、在对象上锁定或在对象上同步。
当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。
1个对象只有1个锁。所以,如果1个线程取得该锁,就没有其他线程可以取得锁,直到第1个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
释放锁是指持锁线程退出了synchronized同步方法或代码块。
Java中的同步块用synchronized标记。同步块在Java中是同步在某个对象上。所有同步在1个对象上的同步块在同时只能被1个线程进入并履行操作。所有其他等待进入该同步块的线程将被阻塞,直到履行该同步块中的线程退出。
有4种不同的同步块:
1. 实例方法
2. 静态方法
3. 实例方法中的同步块
4. 静态方法中的同步块
上述同步块都同步在不同对象上。实际需要那种同步块视具体情况而定。
实例方法同步
下面是1个同步的实例方法:
public synchronized void add(int value){
this.count += value;
}
注意在方法声明中同步(synchronized )关键字。这告知Java该方法是同步的。
Java实例方法同步是同步在具有该方法的对象上。这样,每一个实例其方法同步都同步在不同的对象上,即该方法所属的实例。只有1个线程能够在实例方法同步块中运行。如果有多个实例存在,那末1个线程1次可以在1个实例同步块中履行操作。1个实例1个线程。
静态方法同步
静态方法同步和实例方法同步方法1样,也使用synchronized 关键字。Java静态方法同步以下示例:
public static synchronized void add(int value){
count += value;
}
一样,这里synchronized 关键字告知Java这个方法是同步的。
静态方法的同步是指同步在该方法所在的类对象上。由于在Java虚拟机中1个类只能对应1个类对象,所以同时只允许1个线程履行同1个类中的静态同步方法。
对不同类中的静态同步方法,1个线程可以履行每一个类中的静态同步方法而无需等待。不管类中的那个静态同步方法被调用,1个类只能由1个线程同时履行。
实例方法中的同步块
有时你不需要同步全部方法,而是同步方法中的1部份。Java可以对方法的1部份进行同步。
在非同步的Java方法中的同步块的例子以下所示:
public void add(int value){
synchronized(this){
this.count += value;
}
}
示例使用Java同步块构造器来标记1块代码是同步的。该代码在履行时和同步方法1样。
注意Java同步块构造器用括号将对象括起来。在上例中,使用了“this”,即为调用add方法的实例本身。在同步构造器中用括号括起来的对象叫做监视器对象。上述代码使用监视器对象同步,同步实例方法使用调用方法本身的实例作为监视器对象。
1次只有1个线程能够在同步于同1个监视器对象的Java方法内履行。
下面两个例子都同步他们所调用的实例对象上,因此他们在同步的履行效果上是等效的。
public class MyClass {
public synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}
public void log2(String msg1, String msg2){
synchronized(this){
log.writeln(msg1);
log.writeln(msg2);
}
}
}
在上例中,每次只有1个线程能够在两个同步块中任意1个方法内履行。
如果第2个同步块不是同步在this实例对象上,那末两个方法可以被线程同时履行。
静态方法中的同步块
和上面类似,下面是两个静态方法同步的例子。这些方法同步在该方法所属的类对象上。
public class MyClass {
public static synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}
public static void log2(String msg1, String msg2){
synchronized(MyClass.class){
log.writeln(msg1);
log.writeln(msg2);
}
}
}
这两个方法不允许同时被线程访问。
如果第2个同步块不是同步在MyClass.class这个对象上。那末这两个方法可以同时被线程访问。
使用同步方法1些注意细节:
1、线程同步的目的是为了保护多个线程反问1个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每一个对象都有切唯一1个锁,这个锁与1个特定的对象关联,线程1旦获得了对象锁,其他访问该对象的线程就没法再访问该对象的其他同步方法。
3、对静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。1个线程取得锁,当在1个同步方法中访问另外对象上的同步方法时,会获得这两个对象锁。
4、对同步,要时刻苏醒在哪一个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程没法访问竞争资源。
6、当多个线程等待1个对象锁时,没有获得到锁的线程将产生阻塞。
7、死锁是线程间相互等待锁锁酿成的,在实际中产生的几率非常的小。
在java 5.0之前,在调和对象同享的访问时可使用的机制只有synchronized和volatile。java 5.0增加了1种新的机制ReentrantLock。与之条件到过的机制相反,ReentrantLock其实不是1种替换内置加锁的方法,而是当内置加锁机制不适用时,作为1种可选择的高级功能。
Lock 接口的源码:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock( long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
与内置加锁机制不同,lock接口提供了1种无条件的、可轮询的、定时的和可中断的锁获得机制,所有的加锁和解锁方法都是显示的,在Lock的实现中必须提供与内部锁相同的内存可见性语义,但在加锁语义、调度算法、顺序保证和性能特性方面可以有所不同。
ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。
public class ReentrantLockextends Objectimplements Lock, Serializable
与synchronized1样,ReentrantLock还提供了可重入的加锁语义,与synchronized相比他还为处理锁机制不可用性问题提供了更高的灵活性
为何要创建1种与内置锁如此相近的新加锁机制?
在大多数情况下,内置锁都能够很好的工作,但在功能上存在1些局限性,例如:没法提供中断1个正在等待获得锁的线程或没法在要求获得1个锁时无穷等待下去。内置锁必须在获得该锁的代码中释放,这虽然简化了编码工作(还能与异常处理操作很好的实现交互),但却没法实现非阻塞结构的加锁规则。
使用Lock接口的标准情势以下:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
注意必须在finally中释放锁,由于如果在try中出现了异常,并且没有在finally中进行锁的释放,那末该锁就永久没法释放了。还需斟酌在try中抛出异常的情况,如果可能使对象处于某种不1致的状态,那末就需要更多的try-catch或try-finally代码快。
可定时的与可轮询的锁获得模式是由tryLock方法实现的,与无条件的锁获得模式相比,它具有更完善的毛病恢复机制。
轮询锁:
利用tryLock来获得两个锁,如果不能同时取得,那末回退并重新尝试。
public boolean transferMoney(Account fromAcct,
Account toAcct,
DollarAmount amount,
long timeout,
TimeUnit unit)
throws InsufficientFundsException, InterruptedException {
long fixedDelay = getFixedDelayComponentNanos(timeout, unit);
long randMod = getRandomDelayModulusNanos(timeout, unit);
long stopTime = System.nanoTime() + unit.toNanos(timeout);
while (true) {
if (fromAcct.lock.tryLock()) {
try {
if (toAcct.lock.tryLock()) {
try {
if (fromAcct.getBalance().compareTo(amount) < 0)
throw new InsufficientFundsException();
else {
fromAcct.debit(amount);
toAcct.credit(amount);
return true;
}
}
finally {
toAcct.lock.unlock();
}
}
}
finally {
fromAcct.lock.unlock();
}
}
if (System.nanoTime() < stopTime)
return false;
NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
}
}
索取锁的时候可以设定1个超时时间,如果超过这个时间还没索取到锁,则不会继续梗塞而是放弃此次任务,示例代码以下:
public boolean trySendOnSharedLine(String message,
long timeout, TimeUnit unit)
throws InterruptedException {
long nanosToLock = unit.toNanos(timeout)
- estimatedNanosToSend(message);
if (!lock.tryLock(nanosToLock, NANOSECONDS))
return false;
try {
return sendOnSharedLine(message);
} finally {
lock.unlock();
}
}
还有可中断锁的获得和非块结构加锁、读写锁,加锁一样还有性能斟酌因素,和锁的公平性,和如何选择ReentrantLock和Synchronized,这些将在下篇博客介绍。
下一篇 MSDTC配置