引入Lock的原因
Java提供了一个关键字synchronized用来保证同步访问,那为什么还需要使用Lock?最根本的原因就是使用synchronized会造成线程的阻塞,资源同时只能被一个线程访问,程序效率低。比如,多个线程查询读写文件的时候必须使用同步访问,但是如果使用synchronized的话,不管是读操作还是写操作,都只能有一个线程访问,实际上如果所有的线程都是读取资源文件,不去做同步操作也是可以的,只有当对文件做写操作的时候才需要对资源加锁,防止文件在写入的时候有其他的线程读取文件内容。Lock的引入就可以解决这种问题。
还有一点就是synchronized没有办法显式的释放锁,synchronized只能在代码运行结束或者出现异常的时候才可以释放锁,而Lock可以显示的释放锁,这一点,让同步的机制更加灵活,开发也更加灵活。
Lock中常用的方法
Lock在是Java中的一个接口,里面声明了一些方法:
- lock():用来获取锁,如果没有获取到,则等待。
- unLock():释放锁,需要注意的是Lock是需要显式的释放锁,所以需要将lock()放在try块中,unLock()方法放在finally块中,防止异常造成死锁现象。
- tryLock():尝试获取锁,如果获取到锁,则返回true,否则返回false,这个方法不是阻塞的,也就是说会立刻返回结果。
- tryLock(long time, TimeUnit unit):这个方法和tryLock()类似,不一样的是这个方法如果没有获取到锁,会等待指定的时间,如果指定的时间没有获取到锁,返回false,获取到锁则返回true。
- lockInterruptibly():当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。
ReentrantLock
ReentrantLock是Lock的唯一实现,这是一个重入锁。主要实现了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 35 36
| public class ReentrantLockThread implements Runnable {
private Integer value = 0; private Lock lock = new ReentrantLock();
@Override public void run() {
try { lock.lock(); value++; System.out.println(Thread.currentThread().getName() + "获取了锁: value is " + value); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + "释放了锁: value is " + value);
}
}
public static void main(String[] args) {
ReentrantLockThread reentrantLockThread = new ReentrantLockThread();
Thread thread1 = new Thread(reentrantLockThread, "thread1"); Thread thread2 = new Thread(reentrantLockThread, "thread2"); Thread thread3 = new Thread(reentrantLockThread, "thread3");
thread1.start(); thread2.start(); thread3.start(); }
}
|
运行结果为:
1 2 3 4 5 6
| thread1获取了锁: value is 1 thread1释放了锁: value is 1 thread2获取了锁: value is 2 thread2释放了锁: value is 2 thread3获取了锁: value is 3 thread3释放了锁: value is 3
|
ReadWriteLock
ReadWriteLock是Java中定义的一个接口,主要是用来表示读写锁的。ReadWriteLock中主要有两个方法:
- readLock():返回一个读锁
- writeLock():返回一个写锁
ReentrantReadWriteLock
ReentrantReadWriteLock是ReadWriteLock的实现,里面实现了获取读写锁的逻辑和其他的一些方法。具体的使用方法如下:
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
| public class ReadWriteLockDemo {
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void print() { for (int i = 0; i < 10; i++) { try { readWriteLock.readLock().lock(); System.out.println( Thread.currentThread().getName() + "读取" + i + ",时间为:" + System.currentTimeMillis()); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.readLock().unlock(); }
} }
public static void main(String[] args) {
ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
new Thread(() -> { readWriteLockDemo.print(); }).start(); new Thread(() -> { readWriteLockDemo.print(); }).start(); new Thread(() -> { readWriteLockDemo.print(); }).start();
} }
|
运行结果如下,可以看到在同一个时间点,不同的线程也在打印:
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
| Thread-0读取0,时间为:1516200749523 Thread-0读取1,时间为:1516200749524 Thread-0读取2,时间为:1516200749524 Thread-0读取3,时间为:1516200749524 Thread-0读取4,时间为:1516200749525 Thread-0读取5,时间为:1516200749525 Thread-0读取6,时间为:1516200749525 Thread-0读取7,时间为:1516200749525 Thread-1读取0,时间为:1516200749525 Thread-1读取1,时间为:1516200749526 Thread-1读取2,时间为:1516200749526 Thread-0读取8,时间为:1516200749526 Thread-1读取3,时间为:1516200749526 Thread-1读取4,时间为:1516200749526 Thread-1读取5,时间为:1516200749526 Thread-0读取9,时间为:1516200749526 Thread-1读取6,时间为:1516200749526 Thread-1读取7,时间为:1516200749526 Thread-1读取8,时间为:1516200749526 Thread-2读取0,时间为:1516200749526 Thread-2读取1,时间为:1516200749527 Thread-1读取9,时间为:1516200749526 Thread-2读取2,时间为:1516200749527 Thread-2读取3,时间为:1516200749527 Thread-2读取4,时间为:1516200749527 Thread-2读取5,时间为:1516200749527 Thread-2读取6,时间为:1516200749527 Thread-2读取7,时间为:1516200749527 Thread-2读取8,时间为:1516200749527 Thread-2读取9,时间为:1516200749527
|
关于读写锁,如果一个线程已经获取了读锁,其他的线程还可以获取到读锁,不能获取到写锁。如果一个线程获取到了写锁,其他的线程读锁和写锁都不能获取到,只能等待锁释放。
Lock和synchronized的异同
相同点
- Lock和synchronized都是重入锁,都可以实现同步
区别
- Lock是一个接口,synchronized是一个关键字,是Java的内置实现。
- Lock是一个可中断的锁,可以让等待的线程中断等待,而synchronized不可以中断,只能等待代码执行完成或者出现异常
- Lock必须手动获取锁,同时也必须手动的释放锁,使用的时候需要考虑死锁的问题,而synchronized不需要手动释放,不会存在死锁的问题
- Lock是可以判断是否获取到锁的,使用更加灵活
- Lock在读写操作的场景下效率比synchronized高
- Lock可以实现公平锁,而synchronized不是公平锁。公平锁是以请求锁的顺序来获取锁。比如同时有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程会获得该锁。Lock通过在创建对象的时候指定是否为公平锁。