死锁产生的条件:
1.互斥使用:当资源被一个线程使用时,别的线程不能使用
2.不可抢占:资源请求者不能强制从资源占有手中夺取资源,资源只能由占有者主动释放
3.请求和保持,即当资源请求者在请求其他资源的同时保持对原有资源的占用
4.循环等待,即线程A持有锁L1之后去申请L2锁,线程B持有L2的锁之后去申请L1的锁,两个都在等对方释放锁,即造成了循环等待的情况
种类
1.静态顺序锁,就是我们加synchronized的顺序不一致,导致加锁的对象都在等待对方释放锁
class StaticLockOrderDeadLock {
private final Object lockA = new Object();
private final Object lockB = new Object();
public void a() {
synchronized (lockA) {
synchronized (lockB) { System.out.println("function a");
} }
}
public void b() {
synchronized (lockB) {
synchronized (lockA) { System.out.println("function b");
} }
} }
解决方法:加多个锁的顺序一致
2.动态锁
因为调用方法所传递的参数不一样,之后会导致加锁的顺序不一样,这里的情形是A向B转账,先对A加锁,之后对B加锁,但是会存在一种情况就是A向B转账的同时B也向A转账,这时就会造成死锁
class DynamicLockOrderDeadLock {
public void transefMoney(Account fromAccount, Account toAcco
unt, Double amount) {
synchronized (fromAccount) {
synchronized (toAccount) {
//...
fromAccount.minus(amount);
toAccount.add(amount); //...
} }
解决方法:使用System.identifyHashCode()来定义锁的顺序,或者用对象唯一的id来决定锁的顺序
//正确的代码
class DynamicLockOrderDeadLock {
private final Object myLock = new Object();
public void transefMoney(final Account fromAccount, final Account toAccount, final Double amount) {
class Helper {
public void transfer() {
//...
fromAccount.minus(amount); toAccount.add(amount); //...
} }
int fromHash = System.identityHashCode(fromAccount); int toHash = System.identityHashCode(toAccount);
if (fromHash < toHash) {
synchronized (fromAccount) {
synchronized (toAccount) {
new Helper().transfer();
}
}
} else if (fromHash > toHash) {
synchronized (toAccount) {
synchronized (fromAccount) { new Helper().transfer();
}
}
} else {
synchronized (myLock) {
synchronized (fromAccount) {
synchronized (toAccount) {
}
}
}
}
}
3.协作对象之间发生的死锁
即在锁方法里面调用外部方法
public synchronized void setLocation(Point location) {
this.location = location;
if (location.equals(destination))
dispatcher.notifyAvailable(this);//外部调用方法,可能等 待Dispatcher对象锁
}
正确的代码
public void setLocation(Point location) {
boolean flag = false;
synchronized (this) {
this.location = location;
flag = location.equals(destination);
}
if (flag) dispatcher.notifyAvailable(this);//使用开放调用
}
解决方法:避免在持有锁的情况下调用外部方法
Synchronized关键字(可重入互斥锁)
为什么synchronized可以实现同步?
java中的每一个对象都可以作为锁,当线程访问同步代码时需要先获得对象锁,退出时或异常抛出时释放锁
原理:每个对象都有一个Moniter关联,也就是跟JVM有关,jvm自动释放锁
![](https://img.haomeiwen.com/i17638470/ce642d9336a8dcdd.png)
表现:同步代码块和方法同步
public class synchronizedTest implements Runnable {
static synchronizedTest instance=new synchronizedTest();
public void run() {
synchronized(instance){
//同步代码块,对应文章中第3点
//*******
}
}
void synchronized method1() {} //类中的同步方法 对应文章中第1点
void static synchronized method2() {} ////类中静态同步方法 ,相当于对类加锁
}
1.锁住对象
a. public synchronized void method1
b. synchronized(this){ //TODO }
2.锁住类,即当前对象的Class
a.public synchronized static void method3
b. synchronized(Test.class){ //TODO}
c. synchronized(o) {}(代码块同步,这里面的o可以是任何对象)
ReentrantLock锁(可重入互斥锁)
特点:获取锁与释放锁需要自己操作,还可以中断获取锁以及超时获取锁
Lock接口的主要方法
1.void lock(): 执行此方法时,如果锁处于空闲状态,当前线程将获取到锁。相 反,如果锁已经被其他线程持有,将禁用当前线程,直到当前线程获取到锁。
2.boolean tryLock(): 如果锁可用,则获取锁,并立即返回true,否则返回 false. 该方法和lock()的区别在于,tryLock()只是"试图"获取锁,如果锁不可 用,不会导致当前线程被禁用,当前线程仍然继续往下执行代码。而lock()方法 则是一定要获取到锁,如果锁不可用,就一直等待,在未获得锁之前,当前线程 并不继续向下执行. 通常采用如下的代码形式调用tryLock()方法:
3.void unlock(): 执行此方法时,当前线程将释放持有的锁. 锁只能由持有者释 放,如果线程并不持有锁,却执行该方法,可能导致异常的发生.
4.Condition newCondition(): 条件对象,获取等待通知组件。该组件和当前的 锁绑定,当前线程只有获取了锁,才能调用该组件的await()方法,而调用后, 当前线程将缩放锁。
使用:
ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁 .....................
lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果
try {
//操作
} finally {
lock.unlock(); //释放锁
}
区别
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的 语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现 象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造 成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用 synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
- sychronized是非公平锁,reentrantLock是公平锁
线程间通信的两种方式
1.wait()/notify()
Object类中相关的方法有notify方法和wait方法。因为wait和notify方法定义在Object 类中,因此会被所有的类所继承。这些方法都是final的,即它们都是不能被重写 的,不能通过子类覆写去改变它们的行为。
1wait()方法: 让当前线程进入等待,并释放锁。
2wait(long)方法: 让当前线程进入等待,并释放锁,不过等待时间为long,超过这个时间没有对当前线程进行唤醒,将自动唤醒。
3notify()方法: 让当前线程通知那些处于等待状态的线程,当前线程执行完毕后 释放锁,并从其他线程中唤醒其中一个继续执行。
4notifyAll()方法: 让当前线程通知那些处于等待状态的线程,当前线程执行完毕 后释放锁,将唤醒所有等待状态的线程。
wait()方法使用注意事项:
1当前的线程必须拥有当前对象的monitor,也即lock,就是锁,才能调用wait()方
法,否则将抛出异常java.lang.IllegalMonitorStateException。
2线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知 的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执 行。
3要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在 synchronized方法或synchronized块中。
notify()方法使用注意事项
1如果多个线程在等待,它们中的一个将会选择被唤醒。这种选择是随意的,和具
体实现有关。(线程等待一个对象的锁是由于调用了wait()方法)。
2被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁,当前线程会在方法执行完毕后释放锁。
wait()与sleep()的比较
当线程调用了wait()方法时,它会释放掉对象的锁。
Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会 释放掉对象的锁的。
网友评论