1、解决死锁相较于synchronized独有的方法(特性)
- 响应中断(破坏不可抢占)
void lockInterruptibly() throws InterruptedException;
当调用Interrupt()方法,会抛出一个中断异常,在catch块中捕获这个异常,就会终止线程,释放资源。
class LockTaskOne implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
try {
while (true) {
//lockInterruptibly()尝试获取锁
lock.lockInterruptibly();
}
} catch (Exception e) {
System.out.println("进入catch块,线程终止");
return;
} finally {
lock.unlock();
}
}
}
public class LockInterrupt {
public static void main(String[] args) throws InterruptedException {
LockTaskOne taskOne = new LockTaskOne();
Thread thread = new Thread(taskOne);
thread.start();
TimeUnit.SECONDS.sleep(2);
thread.interrupt();
}
}
- 非阻塞式获取锁(破坏占有且等待)
boolean tryLock();
tryLock()尝试获取锁,如果获取成功,进入同步代码块,如果获取失败,不等待继续往下走。
synchronized是阻塞式获取锁,如果获取失败,就一直卡着。
class LockTaskTwo implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
boolean isLock = false;
try {
if (lock.tryLock()) {
System.out.println(Thread.currentThread().getName() +
"获取锁成功");
TimeUnit.SECONDS.sleep(1);
isLock = true;
} else {
System.out.println("获取锁失败,线程继续运行");
}
} catch (Exception e) {
} finally {
if (isLock) {
lock.unlock();
}
}
}
}
public class TryLockTest {
public static void main(String[] args) {
LockTaskTwo taskTwo = new LockTaskTwo();
Thread threadA = new Thread(taskTwo, "线程A");
Thread threadB = new Thread(taskTwo, "线程B");
threadA.start();
threadB.start();
}
}
- 支持超时(对所有条件都进行破坏)
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
class LockTaskTwo implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
boolean isLock = false;
try {
if (lock.tryLock(2, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() +
"获取锁成功");
TimeUnit.SECONDS.sleep(1);
isLock = true;
} else {
System.out.println("获取锁失败,线程继续运行");
}
} catch (Exception e) {
} finally {
if (isLock) {
lock.unlock();
}
}
}
}
public class TryLockTest {
public static void main(String[] args) {
LockTaskTwo taskTwo = new LockTaskTwo();
Thread threadA = new Thread(taskTwo, "线程A");
Thread threadB = new Thread(taskTwo, "线程B");
threadA.start();
threadB.start();
}
}
这个方法是尝试获取锁,如果失败,等待一段时间,如果在这段时间内获取到锁,就进入同步代码块继续执行,否则线程退出,继续执行。
2、lock的使用格式
try {
// 同步代码块(临界区)
lock.lock();
}catch(Exception e) {
}finally {
// 显示解锁
lock.unlock();
}
3、Lock接口的子类
3.1 ReentrantLock(80%):可重入锁,使用与synchronized基本一致
- 使用ReentrantLock进行同步处理
class Task implements Runnable {
private int ticket = 20;
private Lock lock = new ReentrantLock();
public void run() {
for (int i = 0; i < 20; i++) {
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
lock.lock();
//需要加锁,临界区代码
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() +
"还剩下" + ticket-- + "票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
public class TicketTest {
public static void main(String[] args) {
Task task = new Task();
new Thread(task, "黄牛A").start();
new Thread(task, "黄牛B").start();
new Thread(task, "黄牛C").start();
}
}
synchronized与ReentrantLock的关系与区别?
共性:
1) 都属于独占锁的实现,任意一个时刻只有一个线程能够获取到锁。
2)都支持可重入。区别:
1)synchronized属于JVM层面的管程实现(是C语言实现的,是一个关键字),ReentrantLock属于Java语言层面实现的管程。
2)ReentrantLock有一些synchronized不具备的特性:响应中断,支持超时,支持非阻塞式的获取锁,还可以实现公平锁,支持多个等待队列(synchronized只有一个等待队列)。
公平锁:等待时间最长的线程最先获取锁。
任何一个对象都存在两个队列:
同步队列
:所有获取该对象Monitor失败的线程进入同步队列
等待队列
:调用wait()阻塞的线程进入等待队列
假设现在有5个线程,Thread0,Thread1,Thread2,Thread3,Thread4,Thread0首先获取到Monitor,其它几个线程就会进入同步队列,这时Thread0再调用wait方法阻塞后就会进入等待队列,按理说Thread1可以获得Monitor了,但是如果此时又新开了两个线程Thread5,Thread6,那么肯定是Thread5或者Thread6先获取到Monitor,因为这俩处于运行态,所以说synchronized是一个非公平锁。
ReentrantLock默认是非公平锁,但是它可以传一个参数:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync() ;
}
ReentrantLock可以保证,不管当前有几个线程,只要它在同步队列的队首,只要monitor释放,就一定可以获得锁。
3.2 ReentrantReadWriteLock:读写锁(读者写者模型)
应用:共享单车
App中显示可以用的车辆,不解锁的时候多个人都可以看到这辆车,那么每个人都是一个读锁,当有任何一个人预约之后,预约就是写锁,其他人不能再预约,也就是说,有人骑走之后,APP上就不再显示这辆车,即当有人获取到写锁,读锁也会阻塞(看不到车)。
读者写者模型中,读读异步,读写、写写同步
- 读锁:共享锁,任意一个时刻可以有多个不同线程获取锁
- 写锁:独占锁,任意一个时刻只能有一个线程获取写锁
ReentrantReadWriteLock实现了读写锁(可重入读写锁)
- readLock():获取读锁
- writerLock():获取写锁
应用:使用HashMap+ReentrantReadWriteLock实现多线程缓存
public class Cache {
private Map<String, String> map = new HashMap<>();
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public String getValue(String key) {
//获取到读锁
lock.readLock();
return map.get(key);
}
public void setValue(String key, String value) {
//获取到写锁
lock.writeLock();
map.put(key, value);
}
}
4、Lock接口中最后一个方法newCondition()
Condition newCondition():
每调用一次就产生一个新的Condition(等待队列)对象。
Condition里的await(),signal()方法与wait(),notify()方法用法一样。
区别在于:
原来的wait(),notify()唤醒的是一个队列中的线程,而现在的await(),signal()唤醒的是自己队列中的线程,当前通过哪个condition调用,它就唤醒的是哪个队列中的线程。
应用:使用Lock+Condition实现多生产者消费者模型
这个在我的另一篇文章里,附上链接:
https://blog.csdn.net/Nan_Feng726/article/details/98601732
synchronized与Lock的关系与区别:
1)都可以实现独占锁的实现,都支持可重入的特性。
2)Lock具备一些synchronized不具备特性,响应中断,支持超时,支持非阻塞式获取锁,还可以实现公平锁,支持多个等待队列,支持读写锁。