标签:技术 问题 第一个 方式 private order 底层实现 需要 throws
我们都知道在java中,当多个线程需要并发访问共享资源时需要使用同步,我们经常使用的同步方式就是synchronized关键字,事实上,在jdk1.5之前,只有synchronized一种同步方式。而在jdk1.5中提供了一种新的同步方式--显示锁(Lock)。显示锁是随java.util.concurrent包一起发布的,java.util.concurrent包是并发大神Doug Lea写的一个并发工具包,里面除了显示锁,还有许多其他的实用并发工具类。
什么是显示锁?用一段代码来说明:
package com.gome; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockPractice { private static int a=0; private static Lock lock=new ReentrantLock(); public static void increateBySynchronized(){ a=0; for (int i = 0; i < 1000; i++) { Thread t=new Thread(new Runnable() { public void run() { for (int j = 0; j < 100; j++) { synchronized (LockPractice.class) { a++; } } } }); t.start(); } while (Thread.activeCount()>1) { Thread.yield(); } System.out.println(a); } public static void increateByLock(){ a=0; for (int i = 0; i < 1000; i++) { Thread t=new Thread(new Runnable() { public void run() { for (int j = 0; j < 100; j++) { lock.lock(); try { a++; } finally { lock.unlock(); } } } }); t.start(); } while (Thread.activeCount()>1) { Thread.yield(); } System.out.println(a); } public static void main(String[] args) { increateBySynchronized(); increateByLock(); } }
执行结果:
解释:
类LockPractice 中有两个方法increateBySynchronized()和increateByLock(),这两个方法都成功地用多线程并发将a累加到100000而没有出现竞态条件(race condition)问题。
其中increateBySynchronized()的同步是我们熟悉的synchronized关键字实现的:
synchronized (LockPractice.class) { a++; }
这句代码的含义是:当有一个线程A进入synchronized代码块后,阻塞其他要进入该代码块的线程直到A执行完代码块。synchronized关键字会关联一个锁对象,这里是LockPractice.class。synchronized关键字底层是由jvm来实现的,当一个线程进入synchronized块时,会在关联的锁对象的对象头(MarkWord)中记录下线程信息(可以简单的理解为线程id),这样这个锁对象就被当前线程独占了,其他试图获取这个锁对象的线程将被阻塞。
因此一个线程进入、退出synchronized代码块的本质就是这个线程对锁对象的获取、释放。
而increateByLock()的同步代码如下,其中 lock是全局变量 private static Lock lock=new ReentrantLock();
lock.lock(); try { a++; } finally { lock.unlock(); }
从代码上可以看出,显示锁Lock的使用和synchronized的本质很像,也是定义了一个锁对象(new ReentrantLock()),然后在进入同步代码前加锁,执行同步代码后释放锁。
但是显示锁的底层却和synchronized完全不同,并没有使用到对象头(MarkWord)这样底层的东西,显示锁只是表现出了和synchronized一样的行为(第一个访问同步代码的线程获得锁,阻塞后来的线程)。
从上面的描述来看,显示锁实现了和synchronized一样的功能,但是写起来更复杂(需要手动加锁解锁,还需要写finally防止发生异常后锁不能释放),那为什么还要加入显示锁呢?
我们可以从Lock接口提供的方法看出端倪:
方法名称 | 描述 |
void lock() | 获取锁 |
void lockInterruptibly() throws InterruptedException | 可中断地获取锁,在线程获取锁的过程中可以响应中断 |
boolean tryLock() | 尝试非阻塞获取锁,调用方法后立即返回,成功返回true,失败返回false |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException | 在超时时间内获取锁,到达超时时间将返回false,也可以响应中断 |
void unlock(); | 释放锁 |
Condition newCondition(); | 获取等待组件,等待组件实现类似于Object.wait()方法的功能 |
从Lock提供的接口可以看出来,显示锁至少比synchronized多了以下功能:
其实除了更多的功能,显示锁还有一个很大的优势:synchronized的同步是jvm底层实现的,对一般程序员来说程序遇到出乎意料的行为的时候,除了查官方文档几乎没有别的办法;而显示锁除了个别操作用了底层的Unsafe类之外,几乎都是用java语言实现的,我们可以通过学习显示锁的源码,来更加得心应手的使用显示锁。
当然显示锁也不是完美的,否则java就不会保留着synchronized关键字了,显示锁的缺点主要有两个:
因此当需要进行同步时,优先考虑使用synchronized关键字,只有synchronized关键字不能满足需求时,才考虑使用显示锁。
这篇文章介绍了显示锁是什么,显示锁的优点与缺点,在什么情况下会用到显示锁。
后文将重点学习显示锁的底层实现:队列同步器(AbstractQueuedSynchronizer)的实现、重入锁(ReentrantLock)的实现、读写锁(ReadWriteLock)的实现、等待/通知(Condition)的实现。
标签:技术 问题 第一个 方式 private order 底层实现 需要 throws
原文地址:http://www.cnblogs.com/sheeva/p/6439335.html