在Java多线程中,可以使用synchronized关键字来实现线程之间同步互斥,但在JDK1.5中新增了ReentrantLock类也能达到同样的效果,并且在扩展功能上也更加强大,比如有嗅探锁定、多路分支通知等功能,而且在使用上也比synchronized更加灵活。
下面以一个例子来说明ReentrantLock的使用。
首先是Service类,需要被同步的类。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyService {
private Lock lock = new ReentrantLock(); //持有锁变量
public void testMethod(){
lock.lock(); //在方法内部,第一行加锁
for (int i=0;i<5;i++){
System.out.println("Thread name:"+Thread.currentThread().getName()+" "+(i+1));
}
lock.unlock(); //处理完成之后解锁
}
}
线程类与测试类MyThread,创建4个线程,每个线程用同一个Service对象初始化,线程的run方法即执行service的testMethod方法。
public class MyThread extends Thread{
private MyService service;
public MyThread(MyService service){
super();
this.service = service;
}
@Override
public void run(){
service.testMethod();
}
public static void main(String[] args){
MyService service = new MyService();
MyThread t1 = new MyThread(service);
MyThread t2 = new MyThread(service);
MyThread t3 = new MyThread(service);
MyThread t4 = new MyThread(service);
t1.start();t2.start();t3.start();t4.start();
}
}
程序输出如下,可以看到各个线程之间是同步执行的,一个线程执行完释放锁之后,另一个线程才能执行:
Thread name:Thread-1 1
Thread name:Thread-1 2
Thread name:Thread-1 3
Thread name:Thread-1 4
Thread name:Thread-1 5
Thread name:Thread-2 1
Thread name:Thread-2 2
Thread name:Thread-2 3
Thread name:Thread-2 4
Thread name:Thread-2 5
Thread name:Thread-0 1
Thread name:Thread-0 2
Thread name:Thread-0 3
Thread name:Thread-0 4
Thread name:Thread-0 5
Thread name:Thread-3 1
Thread name:Thread-3 2
Thread name:Thread-3 3
Thread name:Thread-3 4
Thread name:Thread-3 5
另外,当Service类多个方法(比如A、B方法)内部都调用了lock.lock()进行锁定时,当一个线程在方法A中时,另一个线程不能执行B方法。也就是说lock跟synchronized方法一样,调用lock.lock()的线程持有的是“对象锁”(对象监视器)。同一个时刻,只能有一个线程获得对象锁。
关于Lock其他使用如下:
1、公平性与不公平性
锁Lock分为“公平锁”与“不公平锁”,公平锁表示线程获取锁的顺序是按照线程加锁的顺序分配的,机先来先得的FIFO顺序。而不公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁。
在锁的构造函数中有一个如下:
//创建一个具有给定公平策略的 ReentrantLock。
ReentrantLock(boolean fair)
2、方法getHoldCount()
方法getHoldCount()的作用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。使用方法为:
try{
lock.lock();
System.out.println(lock.getHoldCount());
serviceMethod();
}finally{
lock.unlock();
}
3、方法getQueueLength()的作用是返回正等待获取此锁定的线程估计数。
4、方法getWaitQueueLength(Condition condition)的作用是返回等待此锁定相关的给定条件Condition的线程估计数,比如有5个线程,每个线程都执行了同一个condition对象的await()方法,则调用getWaitQueueLength(Condition)方法时返回的int值是5.
5、方法lockInterruptibly()的作用是:如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常
方法boolean tryLock()的作用是,仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定。
方法boolean tryLock(long timeout,TimeUnit unit)的作用是,如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。
参考《Java多线程编程核心技术》
lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放。
参考:Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html
2016.8.9读《实战java高并发程序设计》添加:
1. ReentrantLock称为“可重入锁”,是因为一个线程可以获得多次锁,每次获取锁时都将引用计数增加1。
2. ReentrantLock有个方法lockInterruptibly(),这个方法获取锁,并且自带了相应中断的能力.
3. 重入锁可以使用trylock方法,非阻塞的尝试获取锁。
4. 重入锁有构造方法public ReentrantLock(boolean fair)
可以保证线程获取锁时的公平性,但是性能有影响。
5. ReadWriteLock也应该算是一个优点吧。