java里的Lock提供了比synchronized更灵活的线程同步操作,可以让你更自由的获取锁,释放锁。
官方解释:
Lock implementations provide more extensive locking operations than can be obtained using synchronized methods and statements. They allow more flexible structuring, may have quite different properties, and may support multiple associated Condition objects.
Lock的实现类提供了比synchronized更广泛意义上锁操作,它允许用户更灵活的代码结构,更多的不同特效,支持多个相关的Condition 对象。
这里我们先不去了解什么是Condition对象,我们主要还是看Lock的基本操作,来完成synchroized可以完成的工作。
A lock is a tool for controlling access to a shared resource by multiple threads. Commonly, a lock provides exclusive access to a shared resource: only one thread at a time can acquire the lock and all access to the shared resource requires that the lock be acquired first. However, some locks may allow concurrent access to a shared resource, such as the read lock of a ReadWriteLock.
Lock 是一个对多线程访问共享资源进行控制的工具,通常 一个锁提供了一个共享资源的独占访问, 也就是说在同一时间,只有一个线程可以获取到该锁,在获取锁之后,才能对共享资源进行访问。当然也有的锁允许并发访问资源,例如这种特殊的锁ReadWriteLock
Lock接口这里主要讲两个方法lock()和unlock()
lock()方法用于线程获取锁,当线程获取锁之后,会立即返回,否则该线程会阻塞在这里,直到别的线程释放锁为止。相当于synchroized里获取对象的隐式锁。
unlock() 释放锁,相当于synchroized方法或者synchroized代码段执行结束,但是unlock的执行不要求和lock在同一方法或者同一代码段里,它可以在任何地方,只要和lock方法在同一线程中。
lock() 和 unlock() 必须是成对出现的,在同一个线程中不存在互斥的现象,lock方法可以执行多次,同样的unlock方法也要执行相同的次数,否则该线程的锁将得不到释放。即便该线程执行结束,其它线程也得不到锁对象。所以unlock方法一般写在finally语句中,保证锁对象被释放。
jdk中Lock的实现是:
java.util.concurrent.locks.ReentrantLock
官方实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } } } |
这里不用解释了。在Lock的官方解释中,提到一个ReadWriteLock读写锁,这是为了另外一种多线程场景设计的锁。这个需要进一步解释一下。
很多系统一般都是读操作很多,写的操作很少。虽然读和写,写和写之间是矛盾的,但是读和读之间是不矛盾的,多个线程一起读是不会出现什么多线程问题的,如果所有的读操作都要去获取一个锁之后进行操作,那读的操作效率是很低的。所以就需要这种读写锁。读写锁是对普通Lock的包装再加工,它包含两个锁,读锁和写锁。
如果一个线程需要进行对共享资源的读操作,它首先去申请读锁,如果没有其它线程占用写锁,本线程就可以获得读锁,即便有其它线程也获得了读锁。
如果一个线程需要进行对共享资源的写操作,它首先去申请写锁,那只有在没有任何线程占用写锁或者读锁的情况下,它才可以获得锁。
jdk中对ReadWriteLock的实现是:
java.util.concurrent.locks.ReentrantReadWriteLock
实例:
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | public class Test { private static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); private static Lock readLock = rwLock.readLock(); private static Lock writeLock = rwLock.writeLock(); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 4; i++) { final int num = i; new Thread() { @Override public void run() { try { writeLock.lock(); sleep(1000); System.err.println("线程" + num + "在写"); sleep(1000); System.err.println("线程" + num + "在写"); } catch (InterruptedException e) { } finally { writeLock.unlock(); } } }.start(); } for (int i = 0; i < 4; i++) { final int num = i; new Thread() { @Override public void run() { try { readLock.lock(); sleep(1000); System.err.println("线程" + num + "在读"); sleep(1000); System.err.println("线程" + num + "在读"); } catch (InterruptedException e) { } finally { readLock.unlock(); } } }.start(); } Thread.sleep(500); writeLock.lock(); try { System.err.println("主线程在写"); } finally { writeLock.unlock(); } } } |
输出:
线程0在写
线程0在写
线程1在写
线程1在写
线程2在写
线程2在写
线程3在写
线程3在写
线程0在读
线程2在读
线程1在读
线程3在读
线程0在读
线程1在读
线程2在读
线程3在读
主线程在写
你会发现一个线程占用了写锁之后,其它线程无论读写锁都获取不到锁,直到该线程释放锁。所以上面的写操作是挨个执行的。
如果是读线程站用了读锁之后,其它的读线程还是可以获取到锁进行读操作,所以上面的读操作是乱序的。
同样在读锁被某个线程占用的时候,主线程是获取不到写锁的。所以主线程的写操作,必须是在没有线程占有读锁的时候才会得到锁。
除非注明,赵岩的博客文章均为原创,转载请以链接形式标明本文地址
本文地址:https://zhaoyanblog.com/archives/242.html