从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