引入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
36public 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
6thread1获取了锁: 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
35public 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
30Thread-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通过在创建对象的时候指定是否为公平锁。