从Java 5之后。在java.util.concurrent.locks包下提供了第二种方式来实现同步訪问。那就是Lock。
1.Lock
首先要说明的就是Lock,通过查看Lock的源代码可知,Lock是一个接口:
1
2
3
4
5
6
7
8
|
public interface Lock
{
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock( long time,
TimeUnit unit) throws InterruptedException;
void unlock();
Condition
newCondition();
}
|
以下来逐个讲述Lock接口中每一个方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。unLock()方法是用来释放锁的。
newCondition()这种方法暂且不在此讲述,会在后面的线程协作一文中讲述。
在Lock中声明了四个方法来获取锁,那么这四个方法有何差别呢?
首先lock()方法是寻常使用得最多的一个方法,就是用来获取锁。假设锁已被其它线程获取,则进行等待。
因为在前面讲到假设採用Lock,必须主动去释放锁。而且在发生异常时。不会自己主动释放锁。因此一般来说。使用Lock必须在try{}catch{}块中进行,而且将释放锁的操作放在finally块中进行。以保证锁一定被被释放。防止死锁的发生。
通常使用Lock来进行同步的话,是以以下这样的形式去使用的:
1
2
3
4
5
6
7
8
9
|
Lock
lock = ...;
lock.lock();
try {
//处理任务
} catch (Exception
ex){
} finally {
lock.unlock(); //释放锁
}
|
tryLock()方法是有返回值的,它表示用来尝试获取锁,假设获取成功,则返回true,假设获取失败(即锁已被其它线程获取)。则返回false,也就说这种方法不管怎样都会马上返回。在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,仅仅只是差别在于这种方法在拿不到锁时会等待一定的时间。在时间期限之内假设还拿不到锁。就返回false。
假设假设一開始拿到锁或者在等待期间内拿到了锁。则返回true。
所以,普通情况下通过tryLock来获取锁时是这样使用的:
1
2
3
4
5
6
7
8
9
10
11
12
|
Lock
lock = ...;
if (lock.tryLock())
{
try {
//处理任务
} catch (Exception
ex){
} finally {
lock.unlock(); //释放锁
}
} else {
//假设不能获取锁,则直接做其它事情
}
|
lockInterruptibly()方法比較特殊,当通过这种方法去获取锁时。假设线程正在等待获取锁,则这个线程可以响应中断,即中断线程的等待状态。
也就使说,当两个线程同一时候通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁。而线程B仅仅有在等待。那么对线程B调用threadB.interrupt()方法可以中断线程B的等待过程。
因为lockInterruptibly()的声明中抛出了异常。所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。
因此lockInterruptibly()一般的使用形式例如以下:
1
2
3
4
5
6
7
8
9
|
public void method() throws InterruptedException
{
lock.lockInterruptibly();
try {
//.....
}
finally {
lock.unlock();
}
}
|
注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。由于本身在前面的文章中讲过单独调用interrupt()方法不能中断正在执行过程中的线程,仅仅能中断堵塞过程中的线程。
因此当通过lockInterruptibly()方法获取某个锁时。假设不能获取到,仅仅有进行等待的情况下,是能够响应中断的。
而用synchronized修饰的话。当一个线程处于等待某个锁的状态。是无法被中断的,仅仅有一直等待下去。
2.ReentrantLock
ReentrantLock,意思是“可重入锁”,关于可重入锁的概念在下一节讲述。
ReentrantLock是唯一实现了Lock接口的类,而且ReentrantLock提供了很多其它的方法。以下通过一些实例看详细看一下怎样使用ReentrantLock。
样例1。lock()的正确用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public class Test
{
private ArrayList<Integer>
arrayList = new ArrayList<Integer>();
public static void main(String[]
args) {
final Test
test = new Test();
new Thread(){
public void run()
{
test.insert(Thread.currentThread());
};
}.start();
new Thread(){
public void run()
{
test.insert(Thread.currentThread());
};
}.start();
}
public void insert(Thread
thread) {
Lock
lock = new ReentrantLock(); //注意这个地方
lock.lock();
try {
System.out.println(thread.getName()+ "得到了锁" );
for ( int i= 0 ;i< 5 ;i++)
{
arrayList.add(i);
}
} catch (Exception
e) {
//
TODO: handle exception
} finally {
System.out.println(thread.getName()+ "释放了锁" );
lock.unlock();
}
}
}
|
各位朋友先想一下这段代码的输出结果是什么?
Thread-0得到了锁 Thread-1得到了锁 Thread-0释放了锁 Thread-1释放了锁
或许有朋友会问。怎么会输出这个结果?第二个线程怎么会在第一个线程释放锁之前得到了锁?原因在于。在insert方法中的lock变量是局部变量。每一个线程运行该方法时都会保存一个副本,那么理所当然每一个线程运行到lock.lock()处获取的是不同的锁。所以就不会发生冲突。
知道了原因改起来就比較easy了,仅仅须要将lock声明为类的属性就可以。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public class Test
{
private ArrayList<Integer>
arrayList = new ArrayList<Integer>();
private Lock
lock = new ReentrantLock(); //注意这个地方
public static void main(String[]
args) {
final Test
test = new Test();
new Thread(){
public void run()
{
test.insert(Thread.currentThread());
};
}.start();
new Thread(){
public void run()
{
test.insert(Thread.currentThread());
};
}.start();
}
public void insert(Thread
thread) {
lock.lock();
try {
System.out.println(thread.getName()+ "得到了锁" );
for ( int i= 0 ;i< 5 ;i++)
{
arrayList.add(i);
}
} catch (Exception
e) {
//
TODO: handle exception
} finally {
System.out.println(thread.getName()+ "释放了锁" );
lock.unlock();
}
}
}
|
这样就是正确地使用Lock的方法了。
样例2。tryLock()的用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public class Test
{
private ArrayList<Integer>
arrayList = new ArrayList<Integer>();
private Lock
lock = new ReentrantLock(); //注意这个地方
public static void main(String[]
args) {
final Test
test = new Test();
new Thread(){
public void run()
{
test.insert(Thread.currentThread());
};
}.start();
new Thread(){
public void run()
{
test.insert(Thread.currentThread());
};
}.start();
}
public void insert(Thread
thread) {
if (lock.tryLock())
{
try {
System.out.println(thread.getName()+ "得到了锁" );
for ( int i= 0 ;i< 5 ;i++)
{
arrayList.add(i);
}
} catch (Exception
e) {
//
TODO: handle exception
} finally {
System.out.println(thread.getName()+ "释放了锁" );
lock.unlock();
}
} else {
System.out.println(thread.getName()+ "获取锁失败" );
}
}
}
|
输出结果:
Thread-0得到了锁 Thread-1获取锁失败 Thread-0释放了锁
样例3,lockInterruptibly()响应中断的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
public class Test
{
private Lock
lock = new ReentrantLock();
public static void main(String[]
args) {
Test
test = new Test();
MyThread
thread1 = new MyThread(test);
MyThread
thread2 = new MyThread(test);
thread1.start();
thread2.start();
try {
Thread.sleep( 2000 );
} catch (InterruptedException
e) {
e.printStackTrace();
}
thread2.interrupt();
}
public void insert(Thread
thread) throws InterruptedException{
lock.lockInterruptibly(); //注意,假设须要正确中断等待锁的线程,必须将获取锁放在外面。然后将InterruptedException抛出
try {
System.out.println(thread.getName()+ "得到了锁" );
long startTime
= System.currentTimeMillis();
for (
; ;) {
if (System.currentTimeMillis()
- startTime >= Integer.MAX_VALUE)
break ;
//插入数据
}
}
finally {
System.out.println(Thread.currentThread().getName()+ "运行finally" );
lock.unlock();
System.out.println(thread.getName()+ "释放了锁" );
}
}
}
class MyThread extends Thread
{
private Test
test = null ;
public MyThread(Test
test) {
this .test
= test;
}
@Override
public void run()
{
try {
test.insert(Thread.currentThread());
} catch (InterruptedException
e) {
System.out.println(Thread.currentThread().getName()+ "被中断" );
}
}
}
|
执行之后。发现thread2可以被正确中断。
3.ReadWriteLock
ReadWriteLock也是一个接口。在它里面仅仅定义了两个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public interface ReadWriteLock
{
/**
*
Returns the lock used for reading.
*
*
@return the lock used for reading.
*/
Lock
readLock();
/**
*
Returns the lock used for writing.
*
*
@return the lock used for writing.
*/
Lock
writeLock();
}
|
一个用来获取读锁。一个用来获取写锁。
也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程能够同一时候进行读操作。
以下的ReentrantReadWriteLock实现了ReadWriteLock接口。
4.ReentrantReadWriteLock
ReentrantReadWriteLock里面提供了非常多丰富的方法,只是最基本的有两个方法:readLock()和writeLock()用来获取读锁和写锁。
以下通过几个样例来看一下ReentrantReadWriteLock详细使用方法。
假如有多个线程要同一时候进行读操作的话。先看一下synchronized达到的效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class Test
{
private ReentrantReadWriteLock
rwl = new ReentrantReadWriteLock();
public static void main(String[]
args) {
final Test
test = new Test();
new Thread(){
public void run()
{
test.get(Thread.currentThread());
};
}.start();
new Thread(){
public void run()
{
test.get(Thread.currentThread());
};
}.start();
}
public synchronized void get(Thread
thread) {
long start
= System.currentTimeMillis();
while (System.currentTimeMillis()
- start <= 1 )
{
System.out.println(thread.getName()+ "正在进行读操作" );
}
System.out.println(thread.getName()+ "读操作完成" );
}
}
|
这段程序的输出结果会是,直到thread1运行完读操作之后,才会打印thread2运行读操作的信息。
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0读操作完成
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1读操作完成
而改成用读写锁的话:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public class Test
{
private ReentrantReadWriteLock
rwl = new ReentrantReadWriteLock();
public static void main(String[]
args) {
final Test
test = new Test();
new Thread(){
public void run()
{
test.get(Thread.currentThread());
};
}.start();
new Thread(){
public void run()
{
test.get(Thread.currentThread());
};
}.start();
}
public void get(Thread
thread) {
rwl.readLock().lock();
try {
long start
= System.currentTimeMillis();
while (System.currentTimeMillis()
- start <= 1 )
{
System.out.println(thread.getName()+ "正在进行读操作" );
}
System.out.println(thread.getName()+ "读操作完成" );
} finally {
rwl.readLock().unlock();
}
}
}
|
此时打印的结果为:
Thread-0正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-0正在进行读操作 Thread-0正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-0正在进行读操作 Thread-1正在进行读操作 Thread-0读操作完成 Thread-1读操作完成
说明thread1和thread2在同一时候进行读操作。
这样就大大提升了读操作的效率。
只是要注意的是。假设有一个线程已经占用了读锁,则此时其它线程假设要申请写锁。则申请写锁的线程会一直等待释放读锁。
假设有一个线程已经占用了写锁,则此时其它线程假设申请写锁或者读锁。则申请的线程会一直等待释放写锁。
关于ReentrantReadWriteLock类中的其它方法感兴趣的朋友能够自行查阅API文档
參考:http://www.cnblogs.com/dolphin0520/p/3923167.html