java多线程、并发系列之 (synchronized)同步与加锁机制
SynchronizedJava中每一个对象都有1个内置锁,当程序运行到非静态的synchronized同步方法上时,自动取得与正在履行代码类确当前实例(this实例)有关的锁。取得1个对象的锁也称为获得锁、锁定对象、在对象上锁定或在对象上同步。
1个对象只有1个锁。所以,如果1个线程取得该锁,就没有其他线程可以取得锁,直到第1个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
Java中的同步块用synchronized标记。同步块在Java中是同步在某个对象上。所有同步在1个对象上的同步块在同时只能被1个线程进入并履行操作。所有其他等待进入该同步块的线程将被阻塞,直到履行该同步块中的线程退出。
上述同步块都同步在不同对象上。实际需要那种同步块视具体情况而定。 public synchronized void add(int value){
this.count += value;
}
注意在方法声明中同步(synchronized )关键字。这告知Java该方法是同步的。 静态方法同步 public static synchronized void add(int value){
count += value;
} 一样,这里synchronized 关键字告知Java这个方法是同步的。 实例方法中的同步块 public void add(int value){
synchronized(this){
this.count += value;
}
} 示例使用Java同步块构造器来标记1块代码是同步的。该代码在履行时和同步方法1样。 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个方法内履行。 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);
}
}
} 这两个方法不允许同时被线程访问。 使用同步方法1些注意细节: 1、线程同步的目的是为了保护多个线程反问1个资源时对资源的破坏。 锁机制:在java 5.0之前,在调和对象同享的访问时可使用的机制只有synchronized和volatile。java 5.0增加了1种新的机制ReentrantLock。与之条件到过的机制相反,ReentrantLock其实不是1种替换内置加锁的方法,而是当内置加锁机制不适用时,作为1种可选择的高级功能。
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,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,这些将在下篇博客介绍。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |