一、两种互斥机制
Java 提供了两种互斥锁用来解决在共享资源时存在的并发问题。
一种方式是提供synchronized 关键字,当任务要执行被synchronized 关键字保护的代码片段的时候,它会检查所可用,然后获取锁,执行代码,释放锁。
另一种方式是显式的使用Lock 对象,在Java SE5 中的java.util.concurrent.locks 类库中定义了这个对象,Lock 对象必须被显式的创建,锁定和释放。下面通过一个例子来体验一下两种锁机制有什么不同:创建三个线程并发访问任务,在任务中输出99-1的数字,为了达到效果在输出之前让线程睡了100ms。
synchronized 同步代码方式:
public class SynchronizedTest {
public static void main(String[] args) {
LockDemo2 demo2 = new LockDemo2();
new Thread(demo2,"一号线程").start();
new Thread(demo2,"二号线程").start();
new Thread(demo2,"三号线程").start();
}
}
class LockDemo2 implements Runnable{
private int num = 100;
@Override
public void run() {
while(true){
synchronized(this){
if(num > 1){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"剩余数量为:" + --num);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
显式Lock 锁方式:
public class LockTest {
public static void main(String[] args) {
LockDemo demo = new LockDemo();
new Thread(demo,"一号线程").start();
new Thread(demo,"二号线程").start();
new Thread(demo,"三号线程").start();
}
}
class LockDemo implements Runnable{
private int num = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock();
try {
if(num > 1){
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"剩余数量为:" + --num);
}else{
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
二、对比
上面两种方式都能解决在并发执行时解决共享资源的问题,那么这两种方式有什么不同呢?
synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。 而且它比Lock 显式锁方式写出来的代码要更优雅,但是在解决某些问题时它却不如Lock 灵活,比如:某些遍历并发访问的数据结果的算法要求使用 “hand-over-hand” 或 “chain locking”:获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。
Lock 显式锁正如上面所说的,它比synchronized 更加灵活,而且有的时候解决一些特殊的问题。在使用Lock 时要注意在加锁后一定要释放锁,为了使锁能够得到释放,你必须将unlock() 方法定义在finally 语句块中,这样做也有一个优点就是如果在代码同步的时候出现了错误,你可以在finally 子句对出现的错误进行维护。Lock 的实现类中还提供了很多强大的功能,就留在以后再写吧。