java 使用lock实现同步
同步代码块的另一种机制。
一、是什么
lock是Java提供的同步代码块的另一种机制,它是一种比synchronized关键字更强大也更灵活的机制。这种机制基于Lock接口及其实现类(例如ReentrantLock),提供了更多的好处。
二、为什么使用lock
1.lock支持更灵活的同步代码块结构
使用synchronized关键字时,只能在同一个synchronized块结构中获取和释放控制。Lock接口允许实现更复杂的临界区结构(即控制的获取和释放不出现在同一个块结构中)。
2.lock功能更多
相比synchronized关键字,Lock接口提供了更多的功能。其中一个新的功能是tryLock()方法的实现。这个方法试图获取锁,如果锁已经被其他线程获取,它将返回false并继续往下执行代码。
使用synchronized关键字时,如果线程A试图执行一个同步代码块,而线程B已在执行这个同步代码块,则线程A就会被挂起直到线程B运行完这个同步代码块。使用锁的tryLock()方法,通过返回值将得知是否有其他线程正在使用这个锁保护的代码块。
3.lock性能更好
Lock接口允许分离读和写操作,允许多个读线程和只有一个写线程。相比synchronized关键字,Lock接口具有更好的性能。
三、如何使用
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
package testLock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class test { public static void main(String[] args) { task task = new task(); Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); } } class task implements Runnable { private final Lock lock = new ReentrantLock(); public void test() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + " lock lock"); System.out.println(Thread.currentThread().getName() + " start"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + " end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + " lock release"); } } public void run() { // TODO Auto-generated method stub test(); } } |
输出如下:
1 2 3 4 5 6 7 8 |
Thread-0 lock lock Thread-0 start Thread-0 end Thread-0 lock release Thread-1 lock lock Thread-1 start Thread-1 end Thread-1 lock release |
可以发现,lock.lock()到lock.unlock()之间的代码(共享资源)变成了同步资源。在thread1释放这个lock前,thread2被阻塞,只能等待thread1结束了对同步资源的执行后,才能执行。
实现的效果类似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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package testLock; public class test { public static void main(String[] args) { task task = new task(); Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); } } class task implements Runnable { public void test() { synchronized (this) { try { System.out.println("thread start"); Thread.sleep(2000); System.out.println("thread end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public void run() { // TODO Auto-generated method stub test(); } } |
这里需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区(就是竞争的部分)放在try内,释放锁放在finally内。这里一定要记得手动释放锁,不然就变成死锁了。
我的个人理解:
这里体现了lock更灵活的特点。lock.lock()和lock.unLock()可以在代码的try catch逻辑中加锁,释放锁,粒度比较小。而sychronized普遍修饰一个代码块(类/方法/块),需要仔细考虑代码块中包含哪些逻辑,否则将影响性能。
2.tryLock方法
上面我们提到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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
package testLock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class test { public static void main(String[] args) { task task = new task(); Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); } } class task implements Runnable { private final Lock lock = new ReentrantLock(); public void test() { if (lock.tryLock()) { try { System.out.println(Thread.currentThread().getName() + " lock lock"); System.out.println(Thread.currentThread().getName() + " start"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + " end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + " lock release"); } } else { System.out.println(Thread.currentThread().getName() + " resource is locked ! , do something else !"); } } public void run() { // TODO Auto-generated method stub test(); } } |
结果为:
1 2 3 4 5 |
Thread-0 lock lock Thread-1 resource is locked ! , do something else ! Thread-0 start Thread-0 end Thread-0 lock release |
lock.tryLock()仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回true。如果锁不可用,则此方法将立即返回false。
也就是说,lock.tryLock()可以写在if else语句中,如果lock.tryLock()能成功获取锁(此时已经获取了锁,相当于进行了一次lock.lock()操作),就需要在try catch finally中使用lock.unLock()手动释放锁。如果没有获取锁,就不需要手动释放了,可以做点别的操作。
没有获得锁的时候,不要进行手动释放,否则将会报出java.lang.IllegalMonitorStateException错误。
我的个人理解:
这里体现了lock功能更多的特点。sychronized是自己加锁自己释放,难以控制。而lock则提供了更多的方法,来人为管理锁。
3.读写锁
这里很能体现lock的性能优势。
在对数据进行读写的时候,为了保证数据的一致性和完整性,读和写是互斥的,写和写是互斥的,但是读和读是不需要互斥的,所以加锁的时候应该考虑到以上几种不同情景,加以区分。
如果使用sychronized,很难做到这一点。但lock却可以依靠读写锁来实现。下篇文章再详细实验。
四、总结
lock给java提供了更多的可能性。